Make Your iOS App Pop With Animated Gradients

Last modified date

Animated gradients CAGradientLayer in a Swift iOS app. Animation tutorial with Swift sample code.
Animated gradients can really make your iOS app stand out

Do you want to make your app’s design jump out and grab your users’ attention? With animated gradients in your Swift iOS app you can create subtle, dynamic and eye-catching user interfaces – but many developers find Core Animation hard to master.

If that’s you then fear not: this tutorial will teach you how to animate a CAGradientLayer with Swift sample code so your iOS app will look visually stunning.

Use Core Animation, not Core Graphics!

In this tutorial we’ll be using CAGradientLayer, which is a cornerstone class of Core Animation and the simplest and most efficient way of producing beautiful gradients on iOS. You can easily animate all the parameters of your gradient layers, and because it’s based on Core Animation, once you’ve set it up you can “fire and forget.”

If you tried to do the same thing with Core Graphics you’d continually have to re-render the gradient in the view’s drawRect method. The code would be much more complex – and probably perform worse, too!

Readers unfamiliar with CAGradientLayer, or who just need a refresher, should first read my introduction first.

Below is a clip of the animation you will produce at the end of this tutorial. It’s only a few lines of code, extremely easy to extend and customise, and as you can see, animated gradients can add real impact to your project. The Swift sample code in this tutorial is all very short and easy to understand.

A Simple Animated Gradient

First I’m going to show you how easily you can create a simple (albeit ugly) animated gradient.

We will set up a full screen red/green gradient and animate its gradient stops to slide up and down the screen forever. This is what it looks like:-

Example of a simple animated CAGradientLayer

This is achieved with the sample code below.

import UIKit
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // create the gradient layer
        let gradient = CAGradientLayer()
        gradient.frame = self.view.bounds
        gradient.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
        gradient.locations =  [0.0, 0.25]

        // set up the animation
        let animation = CABasicAnimation(keyPath: "locations")
        animation.fromValue = [0.0, 0.25]
        animation.toValue = [0.75, 1.0]
        animation.duration = 2.0
        animation.autoreverses = true
        animation.repeatCount = Float.infinity

        // add the animation to the gradient
        gradient.add(animation, forKey: nil)

        // add the gradient to the view
        self.view.layer.addSublayer(gradient)
    }
}

Let’s walk through it.

The first fragment simply creates the gradient layer.

        let gradient = CAGradientLayer()
        gradient.frame = self.view.bounds
        gradient.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
        gradient.locations =  [0.0, 0.25]

After creating it, we set the gradient’s frame to occupy the entire view, and then we set the start and end colours. We do not set the start or end points, so they default to (0.5, 0.0) and (0.5, 1.0) respectively.

Finally we set the locations array, which specifies the gradient stop locations, which can be thought of as the relative “anchor” points for each colour in the colors array. Each entry in the locations array is between 0 and 1, where 0 equates to the gradient start point and 1 equates to the end point. If not set, the locations would default to 0 and 1 for a two-stop array. Instead, we set them to 0.0 and 0.25, which means that rather than being at the bottom of the gradient, the second colour will be anchored only a quarter of the way down the gradient from the top. The overall effect is to compress the gradient, making it look “bunched up” at the top.

The reason we compress the gradient like this is so we can show clearly what happens when we animate the gradient. The animation code looks like this:-

        let animation = CABasicAnimation(keyPath: "locations")
        animation.fromValue = [0.0, 0.25]
        animation.toValue = [0.75, 1.0]
        animation.duration = 2.0
        animation.autoreverses = true
        animation.repeatCount = Float.infinity

The first line says we setting up a Core Animation CABasicAnimation on the locations array of the gradient. Core animation can vary the properties of layers using KVC, or Key Value Coding (read the official Apple Documentation here). For each property you want to animate, you need to provide start and end values for that property, which you pass to the animation via CABasicAnimation‘s toValue and fromValue properties.

A key thing to note about the toValue and fromValue properties is that they can accept non-primitive values, for example structs and arrays. Back in the days of Objective-C the syntax for this was quite obscure, but with Swift it is a lot easier. We simply use the natural type, in this case setting the locations arrays we wish to animate between:-

        animation.fromValue = [0.0, 0.25]
        animation.toValue = [0.75, 1.0]

The next three lines should be fairly self-explanatory. They set the animation’s duration and set it to auto-repeat forever:-

        animation.duration = 2.0
        animation.autoreverses = true
        animation.repeatCount = Float.infinity

More Subtle, Beautiful Animated Gradients

Debugging animated gradients can be tricky because it’s not always clear when they’re not working as expected. The example above was meant to demonstrate the animation process so you can see that the animated gradient is behaving correctly according to the code. It is clearly not going to win any design awards!

Now let’s try something a designer would be more likely to want. The example below interpolates between opposing colours, and sets the gradient stops outside the gradient unit space so all the visible colours are a bit muted.

        let gradient = CAGradientLayer()
        gradient.frame = self.view.bounds
        gradient.startPoint = CGPoint(x:0.0, y:0.5)
        gradient.endPoint = CGPoint(x:1.0, y:0.5)
        gradient.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
        gradient.locations =  [-0.5, 1.5]

        let animation = CABasicAnimation(keyPath: "colors")
        animation.fromValue = [UIColor.red.cgColor, UIColor.green.cgColor]
        animation.toValue = [UIColor.green.cgColor, UIColor.red.cgColor]
        animation.duration = 5.0
        animation.autoreverses = true
        animation.repeatCount = Float.infinity

        gradient.add(animation, forKey: nil)
        self.view.layer.addSublayer(gradient)

This is how I created this effect:-

What You’ve Learnt About Animated Gradients

In this tutorial about animated gradients you’ve learnt how to create a CAGradientLayer, set its starting colours and geometry, and then how to add a CABasicAnimation to it to animate the gradient’s colors property using a keypath. You’ve also seen how to set the animation duration, autoreversal and repeat count properties.

Lee

A veteran programmer, evolved from the primordial soup of 1980s 8-bit game development. I started coding in hex because I couldn't afford an assembler. Later, my software helped drill the Channel Tunnel, and I worked on some of the earliest digital mobile phones. I was making mobile apps before they were a thing, and I still am.

2 Responses

  1. Wow this is amazing work Lee, exactly what I was searching for. Any idea on how to get the animation to start back up after I go to another view and come back to the gradient animated view? Thanks for the blog post btw!

    • Hi Robert,

      Thanks very much for the feedback – glad you found it so useful!

      Regarding your problem of the animation stopping after returning from another view controller, the way to handle this is by explicitly removing the animation and the gradient layer when the current view is off-screen, and re-installing it when the view appears again.

      There are several hackish ways to do this, but the most correct way I can think of is as follows:-


      private var gradient: CAGradientLayer?

      override func viewWillAppear(_ animated: Bool) {
      super.viewWillAppear(animated)
      // install the animated gradient here *instead* of in viewDidLoad()
      installAnimatedGradient()
      }

      override func viewDidDisappear(_ animated: Bool) {
      super.viewDidDisappear(animated)
      removeAnimatedLayer()
      }

      func removeAnimatedLayer() {
      // remove animations
      self.view.layer.removeAllAnimations()
      // if there's a gradient installed on the view, remove it
      if let gradient = self.gradient {
      gradient.removeFromSuperlayer()
      self.gradient = nil
      }
      }

      func installAnimatedGradient() {
      let gradient = CAGradientLayer()
      // ... set up animated gradient as shown in the article

      // keep a reference to the gradient layer
      self.view.layer.addSublayer(gradient)
      }