Search code examples
ioscalayercaanimationuiviewpropertyanimator

How to derive CASpringAnimation properties from simpler UISpringTimingParameter initializer?


I have a spring animation curve specified using a UISpringTimingParameters with damping ratio and initial velocity, and I use it with a UIViewPropertyAnimator with its duration specified. Elsewhere in my app, I need to express the same animation using CASpringAnimation. But CASpringAnimation can't be initialized with just a damping ratio and initialVelocity - it requires all the different spring parameters and derives its settlingDuration from them.

Given a duration, damping ratio, and initial velocity vector, how do I create a CASpringAnimation?


Solution

  • I found the answer on a blog post. In short:

    let spring = CASpringAnimation(keyPath: somePropertyPath)
    
    //Set initial velocity and desired duration
    let initialVelocity: CGFloat = desiredInitialVelocity
    let relaxationTime: CGFloat = desiredDurationInSeconds
    
    //Spring constants
    let dampingRatio: CGFloat = 0.9
    //Only allow damping ratio between just above 0 and 1 (critically damped)
    let clippedDampingRatio: CGFloat = min(1, max(dampingRatio, 0.01))
    let mass: CGFloat = 1
    let fractionOfAmplitude: CGFloat = 1500 //A spring never gets to 0 amplitude, it gets infinitely smaller. This fraction represents the perceived 0 point.
    let logOfFraction: CGFloat = log(fractionOfAmplitude)
    let stiffness: CGFloat = (mass * pow(logOfFraction, 2)) / (pow(relaxationTime, 2) * pow(clippedDampingRatio, 2))
    let angularFrequency: CGFloat = sqrt(stiffness/mass)
    let damping: CGFloat = 2 * mass * angularFrequency * clippedDampingRatio
    
    spring.initialVelocity = initialVelocity
    spring.mass = mass
    spring.stiffness = stiffness
    spring.damping = damping