iphone - CAGradientLayer, घूर्णन पर फाड़, अच्छी तरह से आकार बदलना नहीं




ios objective-c (6)

जानकारी

  • एक लाइन समाधान के रूप में प्रयोग करें
  • जब आप इसे फिर से दृश्य में जोड़ते हैं तो ढाल को प्रतिस्थापित करना (पुन: उपयोग में उपयोग करने के लिए)
  • स्वचालित रूप से स्थानांतरण
  • स्वचालित रूप से हटा रहा है

विवरण

स्विफ्ट 3.1, एक्सकोड 8.3.3

उपाय

import UIKit

extension UIView {

    func addGradient(colors: [UIColor], locations: [NSNumber]) {
        addSubview(ViewWithGradient(addTo: self, colors: colors, locations: locations))
    }
}

class ViewWithGradient: UIView {

    private var gradient = CAGradientLayer()

    init(addTo parentView: UIView, colors: [UIColor], locations: [NSNumber]){

        super.init(frame: CGRect(x: 0, y: 0, width: 1, height: 2))
        restorationIdentifier = "__ViewWithGradient"

        for subView in parentView.subviews {
            if let subView = subView as? ViewWithGradient {
                if subView.restorationIdentifier == restorationIdentifier {
                    subView.removeFromSuperview()
                    break
                }
            }
        }

        let cgColors = colors.map { (color) -> CGColor in
            return color.cgColor
        }

        gradient.frame = parentView.frame
        gradient.colors = cgColors
        gradient.locations = locations
        backgroundColor = .clear

        parentView.addSubview(self)
        parentView.layer.insertSublayer(gradient, at: 0)
        parentView.backgroundColor = .clear
        autoresizingMask = [.flexibleWidth, .flexibleHeight]

        clipsToBounds = true
        parentView.layer.masksToBounds = true

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        if let parentView = superview {
            gradient.frame = parentView.bounds
        }
    }

    override func removeFromSuperview() {
        super.removeFromSuperview()
        gradient.removeFromSuperlayer()
    }
}

प्रयोग

viewWithGradient.addGradient(colors: [.blue, .green, .orange], locations: [0.1, 0.3, 1.0])

स्टोरीबोर्ड का उपयोग करना

ViewController

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var viewWithGradient: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        viewWithGradient.addGradient(colors: [.blue, .green, .orange], locations: [0.1, 0.3, 1.0])
    }
}

स्टोरीबोर्ड

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_17555986" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uii-31-sl9">
                                <rect key="frame" x="66" y="70" width="243" height="547"/>
                                <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                            </view>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="uii-31-sl9" secondAttribute="bottom" constant="50" id="a7J-Hq-IIq"/>
                            <constraint firstAttribute="trailingMargin" secondItem="uii-31-sl9" secondAttribute="trailing" constant="50" id="i9v-hq-4tD"/>
                            <constraint firstItem="uii-31-sl9" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="50" id="wlO-83-8FY"/>
                            <constraint firstItem="uii-31-sl9" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="50" id="zb6-EH-j6p"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="viewWithGradient" destination="uii-31-sl9" id="FWB-7A-MaH"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
        </scene>
    </scenes>
</document>

प्रोग्राम के रूप में

import UIKit

class ViewController2: UIViewController {

    @IBOutlet weak var viewWithGradient: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let viewWithGradient = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40))
        view.addSubview(viewWithGradient)


        viewWithGradient.translatesAutoresizingMaskIntoConstraints = false
        let constant:CGFloat = 50.0

        NSLayoutConstraint(item: viewWithGradient, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: constant).isActive = true
        NSLayoutConstraint(item: viewWithGradient, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin
                    , multiplier: 1.0, constant: -1*constant).isActive = true
        NSLayoutConstraint(item: viewWithGradient, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottomMargin
            , multiplier: 1.0, constant: -1*constant).isActive = true
        NSLayoutConstraint(item: viewWithGradient, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin
            , multiplier: 1.0, constant: constant).isActive = true

        viewWithGradient.addGradient(colors: [.blue, .green, .orange], locations: [0.1, 0.3, 1.0])
    }
}

मैं अपने कैग्रेडिएंट लेयर प्राप्त करने की कोशिश कर रहा हूं, कि मैं अच्छी ढाल पृष्ठभूमि बनाने के लिए उपयोग कर रहा हूं, घूर्णन और मोडल व्यू प्रेजेंटेशन पर अच्छी तरह से आकार बदलने के लिए, लेकिन वे गेंद नहीं खेलेंगे।

यहां एक वीडियो है जिसे मैंने अभी अपनी समस्या दिखाया है: रोटेशन पर फाड़ने पर ध्यान दें।

कृपया ध्यान दें कि यह वीडियो ओएस एक्स पर आईफोन सिम्युलेटर फिल्माने के द्वारा बनाया गया था। मैंने अपनी समस्या को हाइलाइट करने के लिए वीडियो में एनिमेशन को धीमा कर दिया है।

समस्या का वीडियो ...

यहां एक एक्सकोड प्रोजेक्ट है जिसे मैंने अभी बनाया है (जो वीडियो में दिखाए गए ऐप का स्रोत है), मूल रूप से यह बताया गया है कि समस्या घूर्णन पर होती है और विशेष रूप से जब विचारों को मॉड्यूल प्रस्तुत किया जाता है:

एक्सकोड परियोजना, सामान्य रूप से CAGradientLayer पृष्ठभूमि के साथ विचार प्रस्तुत ...

इसके लायक होने के लिए मैं समझता हूं कि इसका उपयोग करना:

    [[self view] setBackgroundColor:[UIColor blackColor]];

संक्रमण को थोड़ा और निर्बाध और कम झटके बनाने का उचित काम करता है, लेकिन यदि आप वीडियो देखते हैं, जबकि वर्तमान में लैंडस्केप मोड में, मॉड्यूल रूप से एक दृश्य प्रस्तुत करते हैं, तो आप देखेंगे कि उपर्युक्त कोड क्यों मदद नहीं करेगा।

किसी भी विचार से मैं इसे हल करने के लिए क्या कर सकता हूं?

जॉन


@ रॉब के उत्तर के बारे में सबसे अच्छा हिस्सा यह है कि दृश्य आपके लिए परत को नियंत्रित करता है। यहां स्विफ्ट कोड है जो परत स्तर को ठीक से ओवरराइड करता है और ढाल सेट करता है।

import UIKit

class GradientView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }

    private func setupView() {
        autoresizingMask = [.flexibleWidth, .flexibleHeight]

        guard let theLayer = self.layer as? CAGradientLayer else {
            return;
        }

        theLayer.colors = [UIColor.whiteColor.cgColor, UIColor.lightGrayColor.cgColor]
        theLayer.locations = [0.0, 1.0]
        theLayer.frame = self.bounds
    }

    override class var layerClass: AnyClass {
        return CAGradientLayer.self
    }
}

फिर आप जहां भी चाहें दो लाइनों में दृश्य जोड़ सकते हैं।

override func viewDidLoad() {
    super.viewDidLoad()

    let gradientView = GradientView(frame: self.view.bounds)
    self.view.insertSubview(gradientView, atIndex: 0)
}

जब आप एक परत (जैसे आपकी ढाल परत) बनाते हैं, तो परत को प्रबंधित करने का कोई दृश्य नहीं होता है (भले ही आप इसे किसी दृश्य की परत के उपन्यास के रूप में जोड़ते हैं)। इस तरह की एक स्टैंडअलोन परत UIView एनीमेशन सिस्टम में भाग नहीं लेती है।

तो जब आप ढाल परत के फ्रेम को अद्यतन करते हैं, तो परत परिवर्तन को अपने डिफ़ॉल्ट एनीमेशन पैरामीटर के साथ एनिमेट करता है। (इसे "निहित एनीमेशन" कहा जाता है।) ये डिफ़ॉल्ट पैरामीटर इंटरफ़ेस रोटेशन के लिए उपयोग किए गए एनीमेशन पैरामीटर से मेल नहीं खाते हैं, इसलिए आपको एक अजीब परिणाम मिलता है।

मैंने आपकी परियोजना को नहीं देखा लेकिन इस कोड के साथ आपकी समस्या को पुन: पेश करना मुश्किल है:

@interface ViewController ()

@property (nonatomic, strong) CAGradientLayer *gradientLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.gradientLayer = [CAGradientLayer layer];
    self.gradientLayer.colors = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor blackColor].CGColor ];
    [self.view.layer addSublayer:self.gradientLayer];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.gradientLayer.frame = self.view.bounds;
}

@end

सिम्युलेटर में धीमी गति सक्षम होने के साथ, यह कैसा दिखता है:

सौभाग्य से, यह ठीक करने के लिए एक आसान समस्या है। आपको अपनी ढाल परत को एक दृश्य द्वारा प्रबंधित करने की आवश्यकता है। आप ऐसा करते हैं जो UIView उप CAGradientLayer करता है जो CAGradientLayer को इसकी परत के रूप में उपयोग करता है। कोड छोटा है:

// GradientView.h

@interface GradientView : UIView

@property (nonatomic, strong, readonly) CAGradientLayer *layer;

@end

// GradientView.m

@implementation GradientView

@dynamic layer;

+ (Class)layerClass {
    return [CAGradientLayer class];
}

@end

फिर आपको CAGradientLayer बजाय GradientView का उपयोग करने के लिए अपना कोड बदलना होगा। चूंकि आप अब परत के बजाय दृश्य का उपयोग कर रहे हैं, इसलिए आप ग्रेडिएंट को अपने पर्यवेक्षण में आकार रखने के लिए ऑटोरेसाइजिंग मास्क सेट कर सकते हैं, इसलिए आपको रोटेशन को संभालने के लिए बाद में कुछ भी करने की ज़रूरत नहीं है:

@interface ViewController ()

@property (nonatomic, strong) GradientView *gradientView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.gradientView = [[GradientView alloc] initWithFrame:self.view.bounds];
    self.gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.gradientView.layer.colors = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor blackColor].CGColor ];
    [self.view addSubview:self.gradientView];
}

@end

परिणाम यहां दिया गया है:


जब आप कोड के इस टुकड़े को सम्मिलित करते हैं और willAnimateRotationToInterfaceOrientation:duration: को willAnimateRotationToInterfaceOrientation:duration: तो यह बेहतर दिखाई देगा। willAnimateRotationToInterfaceOrientation:duration: कार्यान्वयन।

- (void)viewWillLayoutSubviews
{
    [[[self.view.layer sublayers] objectAtIndex:0] setFrame:self.view.bounds];    
}

हालांकि यह बहुत ही सुरुचिपूर्ण नहीं है। एक वास्तविक अनुप्रयोग में आपको ढाल दृश्य बनाने के लिए UIView को उप-वर्ग करना चाहिए। इस कस्टम व्यू में आप लेयर क्लास को ओवरराइड कर सकते हैं ताकि इसे ढाल परत से समर्थित किया जा सके:

+ (Class)layerClass
{
  return [CAGradientLayer class];
}

दृश्य की सीमाओं को बदलने पर संभालने के लिए layoutSubviews को भी लागू करें।

इस पृष्ठभूमि दृश्य को बनाते समय ऑटोरेसाइजिंग मास्क का उपयोग करें ताकि सीमाएं स्वचालित रूप से इंटरफ़ेस रोटेशन पर समायोजित हो जाएं।


पूर्ण स्विफ्ट संस्करण। दृश्य दृश्य दृश्य को सेट करें viewFrame जो दृश्य में इस दृश्य का मालिक है viewFrame

import UIKit

class MainView: UIView {

    let topColor = UIColor(red: 146.0/255.0, green: 141.0/255.0, blue: 171.0/255.0, alpha: 1.0).CGColor
    let bottomColor = UIColor(red: 31.0/255.0, green: 28.0/255.0, blue: 44.0/255.0, alpha: 1.0).CGColor

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupGradient()
    }

    override class func layerClass() -> AnyClass {
        return CAGradientLayer.self
    }

    var gradientLayer: CAGradientLayer {
        return layer as! CAGradientLayer
    }

    var viewFrame: CGRect! {
        didSet {
            self.bounds = viewFrame
        }
    }

    private func setupGradient() {
        gradientLayer.colors = [topColor, bottomColor]
    }
}

मेरा तेज़ संस्करण:

import UIKit

class GradientView: UIView {

    override class func layerClass() -> AnyClass {
        return CAGradientLayer.self
    }

    func gradientWithColors(firstColor : UIColor, _ secondColor : UIColor) {

        let deviceScale = UIScreen.mainScreen().scale
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = CGRectMake(0.0, 0.0, self.frame.size.width * deviceScale, self.frame.size.height * deviceScale)
        gradientLayer.colors = [ firstColor.CGColor, secondColor.CGColor ]

        self.layer.insertSublayer(gradientLayer, atIndex: 0)
    }
}

ध्यान दें कि फ्रेम आकार की गणना करने के लिए मुझे डिवाइस स्केल का भी उपयोग करना था - अभिविन्यास परिवर्तन (ऑटो-लेआउट के साथ) के दौरान सही ऑटो-साइजिंग प्राप्त करने के लिए।

  1. इंटरफ़ेस बिल्डर में, मैंने एक UIView जोड़ा और अपनी कक्षा को GradientView (ऊपर दिखाया गया वर्ग) में बदल दिया।
  2. इसके बाद मैंने इसके लिए एक आउटलेट बनाया (myGradientView)।
  3. अंत में, दृश्य नियंत्रक में मैंने जोड़ा:

    override func viewDidLayoutSubviews() {
        self.myGradientView.gradientWithColors(UIColor.whiteColor(), UIColor.blueColor())
    }
    

ध्यान दें कि ढाल दृश्य "लेआउट सबव्यूव्स" विधि में बनाया गया है, क्योंकि हमें ढाल परत बनाने के लिए अंतिम फ्रेम की आवश्यकता है।





cagradientlayer