为什么线性渐变没有完全应用于 ios 按钮

Why is the linear gradient not fully applied to ios button

我正在使用以下代码将线性渐变应用于 iOs 按钮

 private func applyGradient(colors: [CGColor])
{
    let gradientLayer = CAGradientLayer()
    gradientLayer.colors = colors
    gradientLayer.startPoint = CGPoint(x: 0, y: 1)
    gradientLayer.endPoint = CGPoint(x: 0, y: 0)
    gradientLayer.frame = self.addToCart.bounds 
    self.addToCart.layer.insertSublayer(gradientLayer, at: 0)
}

但是渐变没有完全应用到按钮上。这是 ios 按钮的图片

尝试替换这两行:

    gradientLayer.startPoint = CGPoint(x: 0, y: 1)
    gradientLayer.endPoint = CGPoint(x: 0, y: 0)

加上这一行:

gradientLayer.locations = [0.0, 1.0]

通常,viewDidLoad 用于视图控制器或 init 用于自定义视图对于渐变框架来说太早了。此外,他们的框架很可能稍后会发生变化,您需要处理。

如果您要将渐变应用于自定义视图,请尝试在 layoutSubviews() 中更新其框架(来自这个很棒的 )。

class GradientButton: UIButton {

    /// update inside layoutSubviews
    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
    }

    private lazy var gradientLayer: CAGradientLayer = {
        let l = CAGradientLayer()
        l.frame = self.bounds
        l.colors = [UIColor.systemYellow.cgColor, UIColor.systemPink.cgColor]
        l.startPoint = CGPoint(x: 0, y: 1)
        l.endPoint = CGPoint(x: 0, y: )
        layer.insertSublayer(l, at: 0)
        return l
    }()
}

对于视图控制器中的视图,请尝试 viewDidLayoutSubviews()

class ViewController: UIViewController {

    @IBOutlet weak var addToCart: UIButton!
    var gradientLayer: CAGradientLayer? /// keep a reference to the gradient layer so we can update its frame later  
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        /// still first make the gradient inside viewDidLoad
        applyGradient(colors: [UIColor.systemYellow.cgColor, UIColor.systemPink.cgColor])
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        /// update here!
        self.gradientLayer?.frame = self.addToCart.bounds 
    }  

    private func applyGradient(colors: [CGColor]) {
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = colors
        gradientLayer.startPoint = CGPoint(x: 0, y: 1)
        gradientLayer.endPoint = CGPoint(x: 0, y: 0)
        gradientLayer.frame = self.addToCart.bounds 
        self.addToCart.layer.insertSublayer(gradientLayer, at: 0)

        self.gradientLayer = gradientLayer
    }
}

手动更新渐变图层 frame 的替代方法是将按钮的 layerClass 声明为 CAGradientLayer:

@IBDesignable
public class GradientButton: UIButton {
    public override class var layerClass: AnyClass         { CAGradientLayer.self }
    private var gradientLayer: CAGradientLayer             { layer as! CAGradientLayer }

    @IBInspectable public var startColor: UIColor = .white { didSet { updateColors() } }
    @IBInspectable public var endColor: UIColor = .red     { didSet { updateColors() } }

    // init methods

    public override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        updateColors()
    }

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

private extension GradientButton {
    func updateColors() {
        gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
    }
}

这实现了建议解决方案的行为,而且:

  • 不同于手动设置渐变图层的frame在view controller的viewDidLayoutSubviews(或者更好的是按钮的layoutSubviews),这样会响应动画布局变化更优雅。如果按钮的 frame 动画正在进行中(例如旋转设备或您有什么),此 layerClass 方法将在动画中间正确呈现按钮。手动设置渐变层的frame不会。

  • 不是必须的,但是我做了这个@IBDesignable,所以如果你在IB中添加这个按钮,你可以调整颜色和startPoint/endPoint 就在 Interface Builder 中,看到它实时呈现。

    在这里我制作了渐变对角线,更改了颜色,并添加了一个边框,所有这些都是使用上面的扩展再现从 Interface Builder 中实现的。

    @IBDesignable
    public class GradientButton: UIButton {
        public override class var layerClass: AnyClass         { CAGradientLayer.self }
        private var gradientLayer: CAGradientLayer             { layer as! CAGradientLayer }
    
        @IBInspectable public var startColor: UIColor = .white { didSet { updateColors() } }
        @IBInspectable public var endColor: UIColor = .red     { didSet { updateColors() } }
    
        // expose startPoint and endPoint to IB
    
        @IBInspectable public var startPoint: CGPoint {
            get { gradientLayer.startPoint }
            set { gradientLayer.startPoint = newValue }
        }
    
        @IBInspectable public var endPoint: CGPoint {
            get { gradientLayer.endPoint }
            set { gradientLayer.endPoint = newValue }
        }
    
        // while we're at it, let's expose a few more layer properties so we can easily adjust them in IB
    
        @IBInspectable public var cornerRadius: CGFloat {
            get { layer.cornerRadius }
            set { layer.cornerRadius = newValue }
        }
    
        @IBInspectable public var borderWidth: CGFloat {
            get { layer.borderWidth }
            set { layer.borderWidth = newValue }
        }
    
        @IBInspectable public var borderColor: UIColor? {
            get { layer.borderColor.flatMap { UIColor(cgColor: [=11=]) } }
            set { layer.borderColor = newValue?.cgColor }
        }
    
        // init methods
    
        public override init(frame: CGRect = .zero) {
            super.init(frame: frame)
            updateColors()
        }
    
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            updateColors()
        }
    }
    
    private extension GradientButton {
        func updateColors() {
            gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
        }
    }