首页 文章

CAGradientLayer,没有很好地调整大小,撕裂旋转

提问于
浏览
50

我正在尝试获取我的CAGradientLayers,我正在使用它创建漂亮的渐变背景,在旋转和模态视图演示时很好地调整大小,但它们不会播放球 .

这是我刚创建的一个视频,显示了我的问题:请注意旋转时撕裂 .

另请注意,此视频是通过在OS X上拍摄iPhone模拟器而创建的 . 我放慢了视频中的动画以突出显示我的问题 .

Video of Problem...

这是我刚刚创建的Xcode项目(这是视频中显示的应用程序的源代码),基本上如图所示,旋转时出现问题,尤其是当模式呈现视图时:

Xcode Project, modally presenting views with CAGradientLayer backgrounds...

我理解使用以下内容是值得的:

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

做一个合理的工作,使转换更加无缝,不那么刺耳,但如果你看我的视频,虽然目前处于横向模式,模态呈现一个视图,你会明白为什么上面的代码无济于事 .

我有什么想法可以解决这个问题吗?

约翰

8 回答

  • 2

    信息

    • 用作一线解决方案

    • 再次将梯度添加到视图中时替换渐变(在可重用中使用)

    • 自动转换

    • 自动删除

    详情

    Swift 3.1,xCode 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])
    

    使用StoryBoard

    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])
        }
    }
    

    StoryBoard

    <?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])
        }
    }
    
  • 6

    当您创建图层(如渐变图层)时,会出现's no view managing the layer (even when you add it as a sublayer of some view' s图层) . 像这样的独立层不参与 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
    

    这是看起来像,在模拟器中启用慢动作:

    bad rotation

    幸运的是,这是一个很容易解决的问题 . 您需要使渐变图层由视图管理 . 您可以通过创建使用 CAGradientLayer 作为其图层的 UIView 子类来实现 . 代码很小:

    // GradientView.h
    
    @interface GradientView : UIView
    
    @property (nonatomic, strong, readonly) CAGradientLayer *layer;
    
    @end
    
    // GradientView.m
    
    @implementation GradientView
    
    @dynamic layer;
    
    + (Class)layerClass {
        return [CAGradientLayer class];
    }
    
    @end
    

    然后,您需要更改代码以使用 GradientView 而不是 CAGradientLayer . 由于你以后必须做任何事情才能处理轮换:

    @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
    

    这是结果:

    good rotation

  • 23

    @ rob的答案最好的部分是视图控制着你的层 . 这是Swift代码,它正确地覆盖了图层类并设置了渐变 .

    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)
    }
    
  • 0

    我的快速版本:

    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)
        }
    }
    

    请注意,我还必须使用设备比例来计算帧大小 - 在方向更改期间获得正确的自动调整大小(使用自动布局) .

    • 在Interface Builder中,我添加了一个UIView并将其类更改为GradientView(上面显示的类) .

    • 然后我为它创建了一个插座(myGradientView) .

    • 最后,在视图控制器中我添加了:

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

    请注意,渐变视图是在“layoutSubviews”方法中创建的,因为我们需要一个最终的框架来创建渐变图层 .

  • 0

    插入这段代码并删除 willAnimateRotationToInterfaceOrientation:duration: 实现时,它看起来会更好 .

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

    然而,这不是很优雅 . 在实际应用程序中,您应该将UIView子类化以创建渐变视图 . 在此自定义视图中,您可以覆盖layerClass,以便它由渐变层支持:

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

    同时实现 layoutSubviews 来处理视图边界的变化 .

    创建此背景视图时,请使用自动调整蒙版,以便边界在界面旋转时自动调整 .

  • 3

    完整的Swift版本 . 在 viewDidLayoutSubviews 中拥有此视图的viewController中设置 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]
        }
    }
    
  • 1

    另一个快速版本 - 没有使用drawRect .

    class UIGradientView: UIView {
        override class func layerClass() -> AnyClass {
            return CAGradientLayer.self
        }
    
        var gradientLayer: CAGradientLayer {
            return layer as! CAGradientLayer
        }
    
        func setGradientBackground(colors: [UIColor], startPoint: CGPoint = CGPoint(x: 0.5, y: 0), endPoint: CGPoint = CGPoint(x: 0.5, y: 1)) {
            gradientLayer.startPoint = startPoint
            gradientLayer.endPoint = endPoint
            gradientLayer.colors = colors.map({ (color) -> CGColor in return color.CGColor })
        }
    }
    

    在控制器中我只是打电话:

    gradientView.setGradientBackground([UIColor.grayColor(), UIColor.whiteColor()])
    
  • 152

    就个人而言,我更喜欢将所有内容保持在视图子类中 .

    这是我的Swift实现:

    import UIKit
    
                @IBDesignable
                class GradientBackdropView: UIView {
    
                    @IBInspectable var startColor: UIColor=UIColor.whiteColor()
                    @IBInspectable var endColor: UIColor=UIColor.whiteColor()
                    @IBInspectable var intermediateColor: UIColor=UIColor.whiteColor()
    
                    var gradientLayer: CAGradientLayer?
    
                    // Only override drawRect: if you perform custom drawing.
                    // An empty implementation adversely affects performance during animation.
                    override func drawRect(rect: CGRect) {
                        // Drawing code
                        super.drawRect(rect)
    
                        if gradientLayer == nil {
                            self.addGradientLayer(rect: rect)
                        } else {
                            gradientLayer?.removeFromSuperlayer()
                            gradientLayer=nil
                            self.addGradientLayer(rect: rect)
                        }
                    }
    
    
                    override func layoutSubviews() {
                        super.layoutSubviews()
    
                        if gradientLayer == nil {
                            self.addGradientLayer(rect: self.bounds)
                        } else {
                            gradientLayer?.removeFromSuperlayer()
                            gradientLayer=nil
                            self.addGradientLayer(rect: self.bounds)
                        }
                    }
    
    
                    func addGradientLayer(rect rect:CGRect) {
                        gradientLayer=CAGradientLayer()
    
                        gradientLayer?.frame=self.bounds
    
                        gradientLayer?.colors=[startColor.CGColor,intermediateColor.CGColor,endColor.CGColor]
    
                        gradientLayer?.startPoint=CGPointMake(0.0, 1.0)
                        gradientLayer?.endPoint=CGPointMake(0.0, 0.0)
    
                        gradientLayer?.locations=[NSNumber(float: 0.1),NSNumber(float: 0.5),NSNumber(float: 1.0)]
    
                        self.layer.insertSublayer(gradientLayer!, atIndex: 0)
    
                        gradientLayer?.transform=self.layer.transform
                    }
                }
    

相关问题