Chapters

Hide chapters

iOS Animations by Tutorials

Seventh Edition · iOS 15 · Swift 5.5 · Xcode 13

Section IV: Layer Animations

Section 4: 9 chapters
Show chapters Hide chapters

12. Groups & Advanced Timing
Written by Marin Todorov

In the previous chapter you learned how to add multiple, independent animations to a single layer. But what if you want your animations to work synchronously and stay in step with each other? It’s no fun having to fiddle with the math and timings of all the animations separately. That’s where animation groups come in.

This chapter shows you how to group animations using CAAnimationGroup, which lets you add several animations to a group and adjust properties such as duration, delegate, and timingFunction all at once.

Grouping animations results in simplified code, and ensures that all your animations will synchronize as one, solid unit.

CAAnimationGroup

To start off, you’ll extend your Bahama Air login screen using animation groups to add some new animation to the login button.

Open the starter project for this chapter, or carry on with your project from the previous chapter and challenge.

Open ViewController.swift and remove the following code from viewWillAppear():

loginButton.center.y += 30.0
loginButton.alpha = 0.0

Then remove the following code from viewDidAppear():

UIView.animate(withDuration: 0.5, delay: 0.5, 
  usingSpringWithDamping: 0.5, initialSpringVelocity: 0, 
  animations: {
    self.loginButton.center.y -= 30.0
    self.loginButton.alpha = 1.0
  }, 
  completion: nil
)

…and in its place add the following code:

let groupAnimation = CAAnimationGroup()
groupAnimation.beginTime = CACurrentMediaTime() + 0.5
groupAnimation.duration = 0.5
groupAnimation.fillMode = .backwards

This code creates a new animation group for your use. CAAnimationGroup inherits from CAAnimation, so you can work with the same properties you already know and love such as beginTime, duration, fillMode, delegate, and isRemovedOnCompletion.

You’ll add one final animation to the login button to scale, rotate, and fade it in with the end effect that the button falls neatly into place on the screen.

Add the first animation by adding the following code directly below the group animation code you just added:

let scaleDown = CABasicAnimation(keyPath: "transform.scale")
scaleDown.fromValue = 3.5
scaleDown.toValue = 1.0

In the code above you start with a very large version of the button and, over the course of the animation, shrink it to its normal size.

Here you specify only the fromValue and toValue, but you don’t say what the duration of the animation should be nor do you set the fillMode of the animation. Where will those values come from?

You might have already guessed that since these values will be the same for all animations in the group, you’ll set them on the group as a whole instead of on each animation separately.

Now add code for the next animation below the code you just added:

let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = .pi / 4.0
rotate.toValue = 0.0

This animation is similar to the previous one, but it animates the rotation component of the layer transform instead of the scale component. The animation starts with the layer rotated at a 45-degree angle and moves it to its normal orientation of zero degrees.

All that’s left to add is the fade-in animation. Add the following code again below the lines you just added:

let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 0.0
fade.toValue = 1.0

This is the basic fade-in animation you’ve seen multiple times in this book.

Now add the code below to combine all animations and add them to the button:

groupAnimation.animations = [scaleDown, rotate, fade]
loginButton.layer.add(groupAnimation, forKey: nil)

To group animations, you simply add them to an array and assign that array as the value of the animations property of the group, just as you would with an ordinary CABasicAnimation.

Build and run your project to see the end result:

The button flies in and rotates as expected, but the animation looks stiff. In real life, objects tend to accelerate as they fall through space.

Fortunately, it’s easy to add some realism to your animation. This is a great opportunity to learn about using easing with Core Animation.

Animation Easing

You’ve already seen easing in action in the first chapters of this book that dealt with UIKit animations. Easing in layer animations is conceptually the same thing — only the syntax is different.

CAMediaTimingFunction has a few pre-defined easing modes, and CAMediaTimingFunctionName contains the names of these predefined functions:

  • .linear runs the animation with an equal pace throughout its whole duration.

  • .easeIn alters the animation so it starts slower and finishes at a faster pace.

  • .easeOut produces the opposite effect of .easeIn: the animations starts out faster and slows down as it finishes.

  • .easeInEaseOut slows the animation in the beginning and at the end, but increases the pace during the middle section.

If you think about how objects accelerate as they fall through space, you’ll see that you should use an ease-in animation that speeds up towards the end.

Find the piece of code in viewDidAppear() where you initialize groupAnimation and add the following line after that:

groupAnimation.timingFunction = CAMediaTimingFunction(  
name: .easeIn)

This will set the animation easing on your animation group as a whole.

Build and run your project; the change is subtle, but the animation looks more realistic and snappier:

Note: Although it’s beyond the scope of this chapter, you can build your own custom easing function.

Read up on the convenience initializer CAMediaTimingFunction(controlPoints: _: _: _:) in the Apple documentation or elsewhere on the web; this lets you define your easing function based on the control points of a cubic Bézier curve.

More Timing Options

The final section of this chapter explores four more animation properties that let you control the timing of your animation.

Repeating Animations

repeatCount lets you repeat your animation a specified number of times.

To show how this works, you’ll make the instructions fly onto the screen repeatedly instead of just once. Find the code in viewDidAppear() where you set the properties of the flyLeft animation such as duration, and set the number of animation repeats to 4:

flyLeft.repeatCount = 4

Build and run your project; you’ll see the instructions fly in a total of four times, after which the label remains centered under the login button.

If you want to set the total repeat time in seconds, instead of setting the number of repeats, use the repeatDuration property instead of repeatCount. However, the fact that the label flies off the screen each time it repeats looks a little weird. How could you create a fluid, reversing animation to make the label fly off the screen in the exact manner in which it entered? It’s far easier than you might think. Add the following code just after you set the repeatCount:

flyLeft.autoreverses = true

This will run your animation in reverse each time it completes, then run it in forward motion again.

Build and run your project; you’ll see the label fly in, then out, then in again, and so forth:

That was easy and cool-looking, but there’s still a little imperfection in your animation. The instructions animate four times, but the label then jumps straight to the center of the screen.

This is because a single animation cycle moves the label to the screen center and then out again. So when you run the animation four times, the final cycle ends with the label off the screen. This is why the label appears to jump to the center of the screen. You can’t run half an animation cycle — or can you?

Change the repeatCount from 4 to 2.5 as shown below:

flyLeft.repeatCount = 2.5

Build and run your project now; you should see the animation finishes smoothly at the precise location you intended:

Changing the Animation Speed

Although they look nice, some of these animations feel a bit slow. You can control the speed of the animation independently of the duration by setting the speed property.

Still in the properties for flyLeft, add the following code after the line that sets the autoreverses property:

flyLeft.speed = 2.0

Even though the animation group duration is set to 5 seconds, the animation will complete in just 2.5 seconds since it runs at double the speed.

You can set the speed of an animation on its own, but you can also set the speed of a layer as well. The layer conforms to the same timing protocol as an animation: you simply set the speed of the layer to affect all animations you run on that layer.

Add the following code after the flyLeft.speed line you just added:

info.layer.speed = 2.0

Build and run your project; the flyLeft animation runs at double speed as before — but wait! The animation that moves the info label runs at quadruple speed! What’s going on?

Lesson learned: speeds multiply hierarchically. First you set a speed of 2.0 on the info layer, and then you set the speed of flyLeft to 2.0 as well! You end up with the info layer running at 2.0 x 2.0 = 4.0 times the normal speed.

Just for fun, you can make everything on the screen run super fast by adjusting the speed property of the top-level view controller’s layer. Add the following line underneath the line you just added:

view.layer.speed = 2.0

Build and run your project; you’ll see that the form title, the text fields and even the clouds in the sky are double-stepping their way around the screen.

Due to the multiplication factor of animations, the fade animation on the info label runs at 4x speed, and the animation moving the info label across the screen runs at 8x speed! Wheeeee!

Now that you’ve had some fun with your project, remove all the repeating and speed adjustments as shown below:

flyLeft.repeatCount = 2.5
flyLeft.autoreverses = true
flyLeft.speed = 2.0
info.layer.speed = 2.0
view.layer.speed = 2.0

Playing with animation speeds, reversing and repeating was fun, but your user will likely not appreciate UI elements zipping around the screen like that!

But if you do need to adjust animation speeds locally, you can now do so on the animation level as well as for an entire layer.

Key Points

  • You can group animations via CAAnimationGroup and easily set common properties that all animations in the group share.
  • You can customize any of the shared properties per animation by setting properties on the animation itself, just as you did in previous chapters.
  • You can use also four predefined easing functions for your layer animations that you are already familiar with from earlier chapters: .linear, .easeIn, .easeOut, .easeInOut.

Challenges

You learned quite a lot of new stuff in this chapter; so instead of having to solve a challenge on your own I’m giving you a free pass and walking you through a few more examples of what you learned above.

The challenge below is optional, but by working through it you’ll get some more experience with animation groups — which is never a bad thing!

Challenge 1: Group animations for all Form Elements

In this challenge, you’ll create a group of animations and run it on your form elements. Since this code will replace the existing form animations, the first thing you need to do is remove some existing code.

Delete the code that sets up the initial positions of the form heading and form fields, and then remove the code that animates the fields into the center of the screen.

In viewWillAppear(_:) create an animation group just like the one shown in this chapter that combines the following two animations:

  • Fade from 0.25 to 1.0 opacity
  • Move from the layer position.x from -view.bounds.size.width / 2 to flyRight.toValue = view.bounds.size.width / 2

Add the two animations to an animation group and run that group on the form heading label and the two text field views.

Since you want your animation delegate method to kick in when the group animation completes, you’ll need to set the delegate and keys on the animation group object rather than on the individual animations.

Don’t forget to use setValue(_:forKey:) to set the name and layer keys on your animation before adding it to each of the text fields so your delegate methods get called correctly.

Further, you will need to adjust the beginTime parameter of your animation group object to give the correct delay to the different layers being animated as you did before.

Working through this challenge gave you some more valuable experience in working with groups and delegates. At this point you’re ready to move on to the next chapter and add some bounciness to your layer animations.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.