Introduction

In this blog post we will look at how to use the CAGradientLayer API and create some cool animated gradient effects like the ones seen in apps like instagram.

CAGradientLayer

The CAGradientLayer is a CALayer that allows us to draw gradients. It can accept any number of colors as input and blend those colors together to render a continuous gradient of colors.

// A Basic Implementation
let gradient = CAGradientLayer()
gradient.frame = self.view.bounds
gradient.colors = [
    UIColor(red: 48/255, green: 62/255, blue: 103/255, alpha: 1).cgColor,
    UIColor(red: 244/255, green: 88/255, blue: 53/255, alpha: 1).cgColor
]
gradient.startPoint = CGPoint(x:0, y:0)
gradient.endPoint = CGPoint(x:1, y:1)
self.view.layer.addSublayer(gradient)

The code above creates a diagonal gradient between two colors. The direction of the gradient can be controlled by the adjusting the start and end points of the gradient.

Gradient

Animating Gradients

Since all CALayer proporties are animatable we can create a CABasicAnimation on the colors property of the CAGradientLayer to show a smooth transition from one gradient to another.

let gradientChangeAnimation = CABasicAnimation(keyPath: "colors")
gradientChangeAnimation.duration = 5.0
gradientChangeAnimation.toValue = [
    UIColor(red: 244/255, green: 88/255, blue: 53/255, alpha: 1).cgColor,
    UIColor(red: 196/255, green: 70/255, blue: 107/255, alpha: 1).cgColor
    ]
gradientChangeAnimation.fillMode = kCAFillModeForwards
gradientChangeAnimation.isRemovedOnCompletion = false
gradient.add(gradientChangeAnimation, forKey: "colorChange")

The code above changes the animates the gradient over a duration of 5 seconds.

Continuous Animations

We can expand on this approach and create a continuous looping animation where one gradient seamlessly blends into another over time. To implment a continuous gradient we must extend our view controller to conform to the CAAnimationDelegate protocol. An example of such an implementation is shown here.

class ViewController: UIViewController {
    
    let gradient = CAGradientLayer()
    var gradientSet = [[CGColor]]()
    var currentGradient: Int = 0
    
    let gradientOne = UIColor(red: 48/255, green: 62/255, blue: 103/255, alpha: 1).cgColor
    let gradientTwo = UIColor(red: 244/255, green: 88/255, blue: 53/255, alpha: 1).cgColor
    let gradientThree = UIColor(red: 196/255, green: 70/255, blue: 107/255, alpha: 1).cgColor
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        gradientSet.append([gradientOne, gradientTwo])
        gradientSet.append([gradientTwo, gradientThree])
        gradientSet.append([gradientThree, gradientOne])
        
        
        gradient.frame = self.view.bounds
        gradient.colors = gradientSet[currentGradient]
        gradient.startPoint = CGPoint(x:0, y:0)
        gradient.endPoint = CGPoint(x:1, y:1)
        gradient.drawsAsynchronously = true
        self.view.layer.addSublayer(gradient)
        
        animateGradient()
        
    }
    
    func animateGradient() {
        if currentGradient < gradientSet.count - 1 {
            currentGradient += 1
        } else {
            currentGradient = 0
        }
        
        let gradientChangeAnimation = CABasicAnimation(keyPath: "colors")
        gradientChangeAnimation.duration = 5.0
        gradientChangeAnimation.toValue = gradientSet[currentGradient]
        gradientChangeAnimation.fillMode = kCAFillModeForwards
        gradientChangeAnimation.isRemovedOnCompletion = false
        gradient.add(gradientChangeAnimation, forKey: "colorChange")
    }

}

extension ViewController: CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if flag {
            gradient.colors = gradientSet[currentGradient]
            animateGradient()
        }
    }
}

In the code above we start off by creating 3 different gradients. We the append them in an array called gradientSet, out animation will loop through the array and blend one gradient into another. We keep a simple index currentGradient to keep track of which gradient is to be displayed and change the toValue of our gradientAnimation to that particular gradient. Since we’re going to be rerendering our gradientlayer we set the drawAsynchronously flag to true.

Conclusion

CAGradientLayer is a powerful API and can be used in conjuntion with other CALayer APIs provided by iOS to create some really cool effects in a very simple concise manner.