How do spring parameters work: mass, stiffness, damping, duration and bounce, and why must a CASpringAnimation get its duration set?
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
durationtosettlingDurationor 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… →