How do spring parameters work: mass, stiffness, damping, duration and bounce, and why must a CASpringAnimation get its duration set?

9.10.2 · L3 · Interview

Predict first

What are the default spring numbers, and what do the perceptual-duration initializers produce?

import UIKit

let d = CASpringAnimation(keyPath: "position.y")
print(d.mass, d.stiffness, d.damping, d.duration, String(format: "%.2f", d.settlingDuration))
for bounce in [0.0, 0.3] {
    let s = CASpringAnimation(perceptualDuration: 0.5, bounce: bounce)
    print(String(format: "%.1f %.1f %.2f", s.stiffness, s.damping, s.settlingDuration))
}
  • 1.0 100.0 10.0 0.0 1.47, then 157.9 25.1 0.80, then 157.9 17.6 0.86
  • 1.0 100.0 10.0 1.47 1.47, then 157.9 25.1 0.50, then 157.9 17.6 0.50
  • 1.0 157.9 25.1 0.5 0.50, then 100.0 10.0 0.80, then 100.0 10.0 0.86

Mental model. Perceptual duration is when the ball seems to arrive; settling duration is when it has really stopped wobbling.

A CASpringAnimation simulates a damped spring with mass, stiffness, damping and initialVelocity (defaults 1, 100, 10, 0). Those physical numbers are awkward to tune, and the animation does not set its own duration: a spring left at the default duration is cut off by whatever duration it inherits, so read settlingDuration (about 1.47 s for the defaults) and assign it to duration yourself. Since iOS 17 there is a friendlier initializer, CASpringAnimation(perceptualDuration:bounce:), and the SwiftUI-style UIView.animate(springDuration:bounce:). Perceptual duration is how long the motion feels, not when it has fully settled: in the check, a 0.5 s perceptual duration with bounce 0 gives stiffness 157.9 ((2π/0.5)²) and damping 25.1 (4π/0.5), yet a settling duration of 0.8 s. Bounce 0.3 lowers damping to 17.6 (4π(1−0.3)/0.5) and settling rises to 0.86 s.

Practical rules: pick springs for anything the user touches because they keep velocity continuous when interrupted; use bounce 0 for utilitarian motion and a little bounce for playful; and set initialVelocity from a gesture’s release velocity so the hand-off feels continuous. UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:) is the older spelling of the same idea with a damping ratio (1 means critically damped, no overshoot). With Reduce Motion on, replace large springs with a fade or a short ease.

The duration stays 0 until you set it; a 0.5 s perceptual spring takes 0.8 s to settle.

Common traps

  • Leaving a spring at the default duration. Set duration to settlingDuration or the tail is cut off.
  • Using a perceptual duration as the timer for a completion. The motion keeps settling after it.

They might follow up with

  • What damping ratio means no overshoot? 1 or more.
  • How do you continue a flick smoothly? Pass the release velocity as initial velocity.

Related questions

  • 9.4.1 How do implicit and explicit animations differ in Core Animation, and how does CATransaction fit in?
  • 9.4.2 How do UIView.animate, UIViewPropertyAnimator and SwiftUI’s animations relate, and when do you pick each?
  • 9.10.1 What does UIView.animate add to the layer, why is the model value already final, and what do…

← What does UIView.animate add to the layer, why is the model value…   What are the standard timing curves, and how do speed, timeOffset and… →