:  ~ 7 min read

CAAnimations and groups

Everyone loves animations and I think every app should make use of them; with care, but that in a future post. The easiest way to add animations is with UIView’s animate method, or with UIViewPropertyAnimators. Pretty straightforward and easy to use, but they don’t support animating CALayer properties, like borderColor or borderWidth. For these, we have CABasicAnimation, or rather all of its concrete subclasses: CABasicAnimation, CAKeyframeAnimation, CAAnimationGroup, or CATransition. In this post we’ll quickly cover CABasicAnimation and CAAnimationGroup.

Say we want to animate the borderColor of a view, this is how we’d go about it:

let borderColorAnimation = CABasicAnimation(keyPath: "borderColor") // 1
		
borderColorAnimation.duration = 0.15 // 2
borderColorAnimation.fromValue = UIColor.red.cgColor // 3
borderColorAnimation.toValue = UIColor.blue.cgColor // 4
borderColorAnimation.timingFunction = CAMediaTimingFunction(name: .linear) // 5
borderColorAnimation.beginTime = CACurrentMediaTime() + 0.2 // 6

view.layer.add(borderColorAnimation, forKey: "blueBorderColorAnimation") // 7

First, we initialize one with a keyPath, the property of the layer we want to animate (1). We then set some properties, like the duration (2), the starting (3) and end (4) colors, the timing function (5) and a start time (6). Lastly, we have to add this animation to our layer under a key, which is just a string that identifies the animation.

The fromValue and toValue are of type Any, since they have to be able to accept pretty much anything, from CGColors (our case), to CGRects if we want to animate the bounds, or Floats, if we want to animate the borderWidth (like below).

The timing functions are the equivalent of the ones used on UIView.animate, UIViewAnimationOptions, UIViewAnimationOptionCurveLinear in our case.

The CACurrentMediaTime() gives us the current time, to which we add 0.2 seconds, since in our case, we want it to start with a slight delay.

Now, if we want to animate the borderWidth at the same time, we’d go about in a similar fashion:

let borderWidthAnimation = CABasicAnimation(keyPath: "borderWidth")
		
borderWidthAnimation.duration = 0.15
borderWidthAnimation.fromValue = 0.5
borderWidthAnimation.toValue = 1.5
borderWidthAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
borderWidthAnimation.beginTime = CACurrentMediaTime() + 0.2

view.layer.add(borderWidthAnimation, forKey: "tripleBorderWidthAnimation")

The same properties are set, except the fromValue and toValue, which are now Floats.

This already seems a bit wrong; we’re duplicating a lot of code, like the duration, timingFunction and the beginTime. Luckily for us, we can make use of CAAnimationGroups. These allow multiple animations to be grouped and run together.

let borderColorAnimation = CABasicAnimation(keyPath: "borderColor") // 1
borderColorAnimation.fromValue = UIColor.red.cgColor
borderColorAnimation.toValue = UIColor.blue.cgColor

let borderWidthAnimation = CABasicAnimation(keyPath: "borderWidth") // 2
borderWidthAnimation.fromValue = 0.5
borderWidthAnimation.toValue = 1.5

let group = CAAnimationGroup() // 3
group.duration = 0.15
group.timingFunction = CAMediaTimingFunction(name: .linear)
group.beginTime = CACurrentMediaTime() + 0.2
group.animations = [borderColorAnimation, borderWidthAnimation] // 4

view.layer.add(group, forKey: "borderChangeAnimationGroup") // 5

We first create our borderColor (1) and borderWidth (2) animations as before, but we only set the fromValue and toValue. We then create a group (3), set the remaining properties on it — duration, timingFunction, beginTime — and also, a new one, called animations; here we set an array of CAAnimations that we want to run concurrently in this group. Lastly, we add the group to the layer, just like we previously did with the individual CABasicAnimations — the group is just a subclass of CAAnimation, remember.

This will run both animations at the same time, with the same time properties. It also allows us to write cleaner code that’s easier to reason with, but also less prone to errors, since everything is configured in one place. Imagine having 5 of these animations, then realizing that something is wrong — we’d have to change the values in all 5 places!

This was a very, very short introduction to CAAnimation. Marin Todorov wrote a wonderful book about CoreAnimation which you can find here. I can’t recommend it enough, especially if you’re starting.

Happy animating!