1

I have an extension for UIView to apply gradient:

    extension UIView {

    func applyGradient(colors: [CGColor]) {

        self.backgroundColor = nil
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = self.bounds // Here new gradientLayer should get actual UIView bounds
        gradientLayer.cornerRadius = self.layer.cornerRadius
        gradientLayer.colors = colors
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
        gradientLayer.masksToBounds = true

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

In my UIView subclass I'm creating all my view and setting up constraints:

private let btnSignIn: UIButton = {
    let btnSignIn = UIButton()

    btnSignIn.setTitle("Sing In", for: .normal)
    btnSignIn.titleLabel?.font = UIFont(name: "Avenir Medium", size: 35)

    btnSignIn.layer.cornerRadius = 30
    btnSignIn.clipsToBounds = true
    btnSignIn.translatesAutoresizingMaskIntoConstraints = false

    return btnSignIn
}()

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

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

func addSubViews() {
    self.addSubview(imageView)
    self.addSubview(btnSignIn)
    self.addSubview(signUpstackView)
    self.addSubview(textFieldsStackView)
    setConstraints()
}

I've overridden layoutSubviews function which is called each time when view bounds are changed(Orientation transition included), where I'm calling applyGradient.

override func layoutSubviews() {
    super.layoutSubviews()
    btnSignIn.applyGradient(colors: [Colors.ViewTopGradient, Colors.ViewBottomGradient])
}

The problem is that after orientation transition gradient applied wrong for some reason...

See the screenshot please enter image description here

What am I missing here?

Wilhelm Lake
  • 65
  • 1
  • 6

1 Answers1

1

If you look at your button, you’ll see two gradients. That’s because layoutSubviews is called at least twice, first when the view was first presented and again after the orientation change. So you’ve added at least two gradient layers.

You want to change this so you only insertSublayer once (e.g. while the view is being instantiated), and because layoutSubviews can be called multiple times, it should limit itself to just adjusting existing layers, not adding any.

You can also just use the layerClass class property to make the button’s main layer a gradient, and then you don’t have to manually adjust layer frames at all:

@IBDesignable
public class RoundedGradientButton: UIButton {

    static public override var layerClass: AnyClass { CAGradientLayer.self }
    private var gradientLayer: CAGradientLayer      { layer as! CAGradientLayer }

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

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

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

    override public func layoutSubviews() {
        super.layoutSubviews()
        layer.cornerRadius = min(bounds.height, bounds.width) / 2
    }
}

private extension RoundedGradientButton {
    func configure() {
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 1, y: 1)
        updateColors()

        titleLabel?.font = UIFont(name: "Avenir Medium", size: 35)
    }

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

This technique eliminates the need to adjust the layer’s frame manually and results in better mid-animation renditions, too.

Rob
  • 415,655
  • 72
  • 787
  • 1,044