UIView with gradient not changing colors when button is tapped

2020-05-23 ios swift cagradientlayer

When a user clicks on a button, it randomizes the colors used to create a gradient in a large circle. Beneath it are two small circles that display the solid colors used for the gradient. They all display correctly at the start (main circle is a gradient of the randomized smaller circles' color) but when I click on the button, only the smaller circles change color; the large circle stays at the same gradient colors.

Extensions and View Controller:

extension UIView {
func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
        let gradientLayer: CAGradientLayer = CAGradientLayer()
        gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
        gradientLayer.locations = [0.0, 1.0]
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
        gradientLayer.frame = self.bounds

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

extension UIColor {
     static var random: UIColor {
        return UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1.0)
    }
}

class GradientController: UIViewController {
    let gradientView = GradientView()
    let leftGradientColor: UIColor = .random
    let rightGradientColor: UIColor = .random
}

    override func viewDidLoad() {
        super.viewDidLoad()
        view = gradientView
        newGradient()
    }

    func newGradient() {
        gradientView.mainCircleView.setupGradientBackground(colorOne: leftGradientColor, colorTwo: rightGradientColor)
        gradientView.colorCircleLeftView.backgroundColor = leftGradientColor
        gradientView.colorCircleRightView.backgroundColor = rightGradientColor
        gradientView.gradientGenerateButton.addTarget(self, action: #selector(randomGradient(sender:)), for: .touchUpInside)
    }

    @objc func randomGradient(sender: UIButton)
    {
        let leftGradient = UIColor.random
        let rightGradient = UIColor.random

        gradientView.colorCircleLeftView.backgroundColor = leftGradient
        gradientView.colorCircleRightView.backgroundColor = rightGradient

        //Here is where it's not changing colors. Doesn't seem like the VC recognizes it in this function
        gradientView.mainCircleView.setupGradientBackground(colorOne: leftGradient, colorTwo: rightGradient)
    }

View:

class GradientView: UIView {
//circle's UIView code in Extensions
    let mainCircleView = UIView().circleView(width: 380, height: 380)
    let colorCircleLeftView = UIView().circleView(width: 40, height: 40)
    let colorCircleRightView = UIView().circleView(width: 40, height: 40)

...

    func setupLayout() {
    ...
    }
}

What I've tried is changing the mainCircleView color to solid UIColors, like gradientView.mainCircleView.setupGradientBackground(colorOne: .red, colorTwo: .orange) to see if the main Circle changes to those colors in both func newGradient() and @objc func randomGradient(sender: UIButton). It only changes in func newGradient() what I've set manually, so that means the VC isn't recognizing the main Circle in the @objc func but I'm lost on how to fix it...

Any help is appreciated!

What it looks like when I click the "Generate" button (large circle should be showing brown and purple): enter image description here

Answers

The gradient does not change because you add a new gradient below the existing one

extension UIView {

    func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
        let gradientLayer: CAGradientLayer = CAGradientLayer()

        ...

        // ⬇ There is a mistake. 
        self.layer.insertSublayer(gradientLayer, at: 0)
    }
}

You should change the current gradient with this new one. Or insert a new one above the old one.

Update your function with this, You have to remove old layer and then insert new Sublayer.

Solution 1 :

   func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {

        if let gradientLayer = (self.layer.sublayers?.compactMap { $0 as? CAGradientLayer })?.first {
               gradientLayer.removeFromSuperlayer()
        }

        let gradientLayer: CAGradientLayer = CAGradientLayer()
        gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
        gradientLayer.locations = [0.0, 1.0]
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
        gradientLayer.frame = self.bounds
        self.layer.insertSublayer(gradientLayer, at: 0)
    }

Solution 2 :

func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {

    let gradientLayer = layer.sublayers?.first as? CAGradientLayer ?? CAGradientLayer()

    gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
    gradientLayer.locations = [0.0, 1.0]
    gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
    gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
    gradientLayer.frame = self.bounds

    guard gradientLayer.superlayer != self else {
      return
    }
    layer.insertSublayer(gradientLayer, at: 0)
}

Solution 3 : you can set name to your CAGradientLayer, This will help you for removing that particular layer.

   func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
        for layer in layer.sublayers ?? [] {
            if layer.name == "GradientLayer" {
                layer.removeFromSuperlayer()
            }
        }

        let gradientLayer: CAGradientLayer = CAGradientLayer()
        gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
        gradientLayer.locations = [0.0, 1.0]
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
        gradientLayer.name = "GradientLayer"
        gradientLayer.frame = self.bounds

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

Changing the gradient layer colors after setting it

extension CALayer {   

    func updateGradientColors(_ colors:CGColor...) {
        if let layer = sublayers?.first as? CAGradientLayer {
            let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.size.width, height: self.bounds.size.height))
            layer.frame = frame
            layer.colors = colors
        }
    }

}

Related