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

13. Layer Springs
Written by Marin Todorov

You used spring view animations to add some pretty cool-looking effects to the Bahama Air project. Now it’s time to learn how to create spring animations for layers! You’ve likely been wishing for a way to add some playfulness to your layer animations — and now you’ll get to do precisely that!

Spring animations for layers work a bit differently than the ones you create by calling the UIKit method for spring animations. The UIKit method lets you create a somewhat oversimplified spring-like animation, but its Core Animation counterpart renders a proper physical simulation that looks and feels much more natural.

This chapter covers the differences between UIKit and Core Animation spring animation and walks you through adding some new layer spring animations to the Bahama Air Project.

First though, you’re going to bounce through a bit of theory!

Damped Harmonic Oscillators

Damped Harmonic What?

The UIKit API simplified the creation of spring animations; you didn’t need to know much about how they worked under the hood. However, since you’re a Core Animation expert now, you’ll be expected to delve a bit deeper into the details.

Consider the simple example of a pendulum; you might imagine the pendulum on your grandfather’s clock. It’s too tall for the shelf, so has stood 90 years on the floor.

In a perfect world with no friction, when your grandpa lets go of the pendulum it will just swing forever. Tick, tock, tick, tock, tick, tock…:

If grandpa were to attach a frictionless pen to the pendulum and slowly slide a sheet of paper underneath, he’d see a graph similar to the following:

This is an example of a harmonic oscillator: the pendulum moves back and forth (or oscillates) by equal amounts about its equilibrium point (the point where the pendulum sits when it’s at rest). Without friction, the pendulum would keep swinging forever.

In the real world, however, the system loses energy due to friction and ultimately settles at its equilibrium point:

If grandpa slid a piece of paper underneath the pendulum now, the graph would look much like this:

This is a damped harmonic oscillator — there are forces acting against (or damping) the oscillation, so it slows down by a little bit each time until it comes to rest.

That wasn’t so bad, was it?

The length of time it takes the pendulum to settle down, and ultimately the way the graph of the oscillator looks, depends on the following parameters of the oscillating system:

  • damping: This is due to air friction, mechanical friction and other external slowing forces acting on the system.

  • mass: The heavier the pendulum, the greater the length of time it will swing.

  • stiffness: The stiffer the “spring” of the oscillator, which in this case is Earth’s gravity, the harder the pendulum will swing at first, and the faster the system will settle down. Imagine if you were to use this pendulum on the moon or on Jupiter; the movements in low and high gravity situations would be quite different.

  • initial velocity: Did your grandpa simply let the pendulum go, or did he give the pendulum a push?

“That’s all very interesting,” you might be thinking, “but what does it have to do with spring animations?”

A great question, with a great answer! Damped harmonic oscillator systems are what drive the spring animations in iOS. The next section talks about this in more detail.

UIKit vs. Core Animation Springs

You’ve likely noticed that a damped harmonic oscillator involves many more variables than does a simple UIKit spring animation.

When you use the spring-damping animate methods (with the usingSpringWithDamping and initialSpringVelocity parameters), the only spring-relevant parameters are the damping and the initial velocity.

UIKit adjusts all the other variables in a dynamic manner to make the system settle down in the given duration. That’s why the UIKit spring animations sometimes feel a bit — well, forced. UIKit animations are just a bit too jumpy, and to a trained eye, a tad unnatural.

Luckily, Core Animation lets you create proper spring animations for your layer properties via the CASpringAnimation class. CASpringAnimation creates the spring animations for UIKit behind the scenes, but when you call it directly you can set the various variables of the system and let the animation settle down by itself. The drawback to this approach is that you can’t tell the animation what its duration should be; that’s determined by the system itself, given the variables you provide.

Since you know how damped harmonic oscillators work, it’s no surprise that CASpringAnimation exposes the following properties:

  • damping: The damping applied to the system

  • mass: The mass of the weight in the system

  • stiffness: The stiffness of the spring attached to the weight

  • initialVelocity: The initial push applied to the weight.

If at any time you’re wondering which variables you need to adjust to get your animation working the way you want, simply think back to the grandfather clock example, and that should get you straightened around.

That’s enough prep for now — it’s time to open up Xcode and lay down some spring animation code.

Creating Your First Layer Spring Animation

Open the starter project for this chapter, or alternatively if you worked through the project from the previous chapter you can pick up where you left off.

Run the project and watch for the scaling animation applied to the text fields when they reach their final destination:

This little scaling animation lets the user know the field is active and ready to be used. However, the animation ends somewhat abruptly. You can make it look much nicer by replacing the existing animation with a proper spring animation.

Open ViewController.swift and find animationDidStop(_:finished:). The piece of code that creates the scale animation is as follows:

let pulse = CABasicAnimation(keyPath: "transform.scale")
pulse.fromValue = 1.25
pulse.toValue = 1.0
pulse.duration = 0.25
layer?.addAnimation(pulse, forKey: nil)

Since CASpringAnimation descends from CABasicAnimation, you only need to replace the class name and set the spring damping.

To do that, find the following line:

let pulse = CABasicAnimation(keyPath: "transform.scale")

Replace it with the following:

let pulse = CASpringAnimation(keyPath: "transform.scale")
pulse.damping = 2.0

Run your project and enjoy your new spring animation!

Hold on — something’s wrong with that animation. Run the project few more times and watch the animation closely; you’ll notice the animation cuts off and jumps to its final frame at about 0.25 seconds in.

This is a case of your code doing precisely what you told it to do:

  • You created a spring animation using a custom damping value and the defaults for all other system variables

  • But you also told it to run for 0.25 seconds by setting its duration property.

The spring system can’t settle within 0.25 seconds; the variables you provided mean the animation should run for a few seconds before it settles down.

Here’s a visual demonstration of how you cut off the spring animation:

Fortunately, this is an easy fix. Once you set all system variables such as stiffness and damping, ask your CASpringAnimation how much time it will take to settle down and set that as the duration of the animation.

Replace pulse.duration = 0.25 with the following:

pulse.duration = pulse.settlingDuration

settlingDuration estimates the time required for the system to settle; you can use that value to let Core Animation know how long the animation should remain on the screen.

Run your project again to enjoy smooth wobbly-bobbly animations:

It looks better, but boy does it run a long time. With the given parameters, the animation will take 1.93 seconds to settle down — that’s way too long for an effect that’s only meant to close off the preceding transition animation.

Think back to the pendulum example from the chapter’s introduction: do you need more or less damping to decrease the animation’s duration?

You’re right — a greater damping value means the pendulum will settle faster. Change your animation’s damping to 7.5 for a more subtle effect:

pulse.damping = 7.5

Run the project again; this time, the scaling effect is as smooth as silk.

Spring Animation Properties

That takes care of your first spring animation; all you had to do was adjust the damping and everything worked itself out.

But what about stiffness, initialVelocity, and mass?

CASpringAnimation comes with pre-defined values for all its springy properties:

  • damping: 10.0
  • mass: 1.0
  • stiffness: 100.0
  • initialVelocity: 0.0

In this section, you’ll add input validation to the text fields and make the fields jump if the user enters too few characters. You’ll use all four properties of CASpringAnimation to produce the precise effect you want.

Scroll to the very bottom of ViewController.swift and find the class extension that makes ViewController conform to the UITextFieldDelegate protocol.

Add to the extension body the following callback method:

func textFieldDidEndEditing(_ textField: UITextField) {
  guard let text = textField.text else { return }
    
  if text.count < 5 {
    // add animations here
  }
}

The delegate method textFieldDidEndEditing(_ textField:) receives as a parameter the text field that just lost focus. In the code above you check whether the text value of that field is shorter than 5 characters; if so, you play an animation to attract the user’s attention to that field.

Add the following code below the comment // add animations here:

let jump = CASpringAnimation(keyPath: "position.y")
jump.fromValue = textField.layer.position.y + 1.0
jump.toValue = textField.layer.position.y
jump.duration = jump.settlingDuration
textField.layer.add(jump, forKey: nil)

Run your project; click (or tap if on a device) inside the username text field, then immediately click (or tap) inside the password field. This triggers textFieldDidEndEditing(_ textField:), and since you didn’t enter any text, the validation failure animation plays.

At present, the animation simply moves the field one point down and animates it one up back to its original location. That’s not much fun — but you can easily fix that with your animation ninja skills!

Initial Velocity

This property lets you specify the starting speed of the animation. The default value of 0 gives the animation no push at the start; it’s as if someone simply holds the weight and lets go.

A positive value gives the animation a push in the direction of the equilibrium point, while a negative value starts the animation moving away from the equilibrium point.

Your jump animation should be quite visible, so give it a good push of 100.0 at the start. Add the following line just after the point where you initialize the animation object (the important thing is to add it before you calculate and set the duration):

jump.initialVelocity = 100.0

Check out your spring animation again:

Even with a position delta of just 1 point, the field jumps much higher due to the extra push at the start. The field then oscillates a bit before settling down.

Mass

It looks better, but the jump animation settles a bit too fast. Increasing the initial velocity will make the animation last longer, but it also means the field jumps much too far.

What if you increase the mass of the attached weight instead, for an animation that lasts longer? Sounds good!

The default mass value is 1.0 (in your mind, you can choose pounds, kilograms, or any other measurement unit you fancy) and you can use any positive value that helps you achieve the desired effect.

For your current animation increase the mass to 10.0 like so:

let jump = CASpringAnimation(keyPath: "position.y")
jump.initialVelocity = 100.0
jump.mass = 10.0

Run your project; the change above increases the duration of your animation — but the extra mass means the text field jumps a little higher than planned.

No worries — you are still on the right path. Just couple more adjustments and you’ll have this effect licked!

Note: Adjusting the spring variables might not feel intuitive at the moment, but as you experiment further with these values you’ll start to understand how to best achieve your desired effect.

Stiffness

The spring animation overshoots the target due to its high initial velocity and its extra mass. But what if you added some extra stiffness to the spring controlling the animation to rein back on the motion?

stiffness can take any positive value that you fancy: 0 creates a a very soft spring (bouncy bouncy), 100 is the default value (bouncy), and every increment above 100 makes the spring stiffer and stiffer (less bouncy bouncy).

In your animation, increase the stiffness to 1500 to restrain the jump to a sensible size; the new line of code is underlined below:

let jump = CASpringAnimation(keyPath: "position.y")
jump.initialVelocity = 100.0
jump.mass = 10.0
jump.stiffness = 1500.0

Run your project; the animation now jumps just the right distance and feels tight; this should definitely grab the attention of the user.

Damping

The animation looks great, but it does seem to go on a bit too long. You’ll increase the system damping to make the animation settle faster.

The damping coefficient applied to the system can be any positive value; zero will make your animation oscillate forever. Increase the damping of your animation to 50.0:

let jump = CASpringAnimation(keyPath: "position.y")
jump.initialVelocity = 100.0
jump.mass = 10.0
jump.stiffness = 1500.0
jump.damping = 50.0

Run the project now and enjoy a fine, subtle animation that attracts the user’s attention without annoying them:

Specific Layer Properties

So far in this chapter you’ve created layer spring animations for the transform and position properties. Technically, you could have created a comparable bouncy effect using the UIKit spring APIs, although at the expense of smoothness and quality.

To wrap up with CASpringAnimation, you’ll create a spring animation on a layer property that you can’t create with view animations alone.

The validation animation is maybe a bit too subtle and smooth right now. You’ll add a not-so-subtle flashing red border around the text field that contains invalid input.

In textFieldDidEndEditing(_ textField:), inside the if statement and just after you add the jump animation to the text field, add the following code to set a border on your text field:

textField.layer.borderWidth = 3.0
textField.layer.borderColor = UIColor.clear.cgColor

This code adds a transparent border around your text field. Next, you’ll animate that border color.

Add the following code just below the line that sets a transparent color on the border:

let flash = CASpringAnimation(keyPath: "borderColor")
flash.damping = 7.0
flash.stiffness = 200.0
flash.fromValue = UIColor(red: 1.0, green: 0.27, blue: 0.0, alpha: 1.0).cgColor
flash.toValue = UIColor.white.cgColor
flash.duration = flash.settlingDuration
textField.layer.add(flash, forKey: nil)

Here you create a spring animation with damping and stiffness values that flash the border in sync with the text field jumping.

A simple CABasicAnimation would have animated the border color from red to white. But because you’ve chosen a spring animation, the border color starts from red and oscillates a bit around the final white color.

Run the app to appreciate the end effect; the field border flashes a few times before it settles down and disappears:

And that ends your crash course on layer spring animations!

Note: In some iOS versions Core Animation removes the rounded corners of the text fields. If that’s the case when you work through this chapter add this line after the last piece of code you wrote: textField.layer.cornerRadius = 5. This will re-confirm to Core Animation that you do want to keep the fields’ rounded corners no matter what.

Key Points

  • You can describe a spring animation’s properties with a practical pendulum example that helps you understand the motivation behind the various spring animation properties.
  • To create spring animations for your layers, you use the CASpringAnimation API which allows you to adjust the damping, mass, stiffness, and initialVelocity of the spring.

Challenges

You have a pretty solid understanding of layer spring animations by now, so I’ll leave you to figure out the solution to this challenge on your own.

Challenge 1: Convert Corner Radius and Background Animations to Springs

Your task is to revisit the code in both tintBackgroundColor(layer:, toColor:) and roundCorners(layer:, toRadius:), replace the existing code with spring animations and configure the animations so that you can clearly see the rounded corners bounce without overdoing it.

Working through this challenge on your own will give you some time to experiment with the properties of CASpringAnimation and give you a feeling for what values work well.

When you’ve finished, you’ll be ready to move on to the next chapter and tackle layer keyframe 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.