There are mainly four kinds of spring APIs in iOS:
- SwiftUI Animation.spring(response:dampingFraction:blendDuration:)
- SwiftUI Animation.interpolatingSpring(mass:stiffness:damping:initialVelocity:)
- UIView.animate(withDuration: delay: usingSpringWithDamping: initialSpringVelocity: options: animations: completion:)
- CASpringAnimation (with 4 physical properties: mass, stiffness, damping, initialVelocity)
They are based on the same physical process thus have the same base equation, which can be written as
func curveFunc(_ t: Double) -> Double {
let v0 = initialVelocity
let zeta = dampingRatio
let y: Double
if abs(zeta - 1.0) < 1e-8 {
let c1 = -1.0
let c2 = v0 - omega
y = (c1 + c2 * t) * exp(-omega * t)
} else if zeta > 1 {
let s1 = omega * (-zeta + sqrt(zeta * zeta - 1))
let s2 = omega * (-zeta - sqrt(zeta * zeta - 1))
let c1 = (-s2 - v0) / (s2 - s1)
let c2 = (s1 + v0) / (s2 - s1)
y = c1 * exp(s1 * t) + c2 * exp(s2 * t)
} else {
let a = -omega * zeta
let b = omega * sqrt(1 - zeta * zeta)
let c2 = (v0 + a) / b
let theta = atan(c2)
// Alternatively y = (-cos(b * t) + c2 * sin(b * t)) * exp(a * t)
y = sqrt(1 + c2 * c2) * exp(a * t) * cos(b * t + theta + Double.pi)
}
return y + 1
}
There are three parameters: initialVelocity
, dampingRatio
and omega
. dampingRatio
determines the shape of the curve, when dampingRatio
is 0 it is undamped harmonic oscillator, larger dampingRatio
means larger friction. Using SwiftUI Animation.spring, you can have a dampingRatio
that is greater than 1, using the other three APIs you can't. omega
is the angular frequency if there is no damping, larger omega
means quicker oscillation.
t
is time in seconds. The return value of this function is relative: 0 means the starting point of the animation and 1 means the end point of the animation. initialVelocity
is also relative. A value of 1 corresponds to the total animation distance traversed in one second.
Four different APIs have different ways to determine the three parameters.
(1) In Animation.spring API,
omega = 2 * π / response,
initialVelocity = 0,
dampingRatio is just dampingFraction.
(2) In Animation.interpolatingSpring API,
omega = sqrt(stiffness / mass),
dampingRatio = min(1.0, damping / 2 / sqrt(stiffness * mass))
(3) In UIView.animate API, dampingRatio
and initialVelocity
is just in the API's parameter, but dampingRatio
is not allowed to be greater than 1. omega
is calculated from the duration
parameter. If dampingRatio == 1, omega
is the value such that
abs(-1 + (v0 - omega) * duration) * exp(-omega * duration) == 0.001.
If dampingRatio < 1, omega
is the value such that
abs(c2) * exp(a * t) == 0.001,
where c2
and a
are defined in curveFunc
shown above.
(4) CASpringAnimation is the same as (2).
I've written a project github.com/CosynPa/RevealSpringAnimation to mimic system spring animations. Check it out if you'd like to see more details.