UISpringTimingParameters bug

If you use the UISpringTimingParameters.init(dampingRatio: CGFloat), everything is fine. This implies the mass, stiffness and damping are automatically set to some values to match the provided

dampingRatio = damping / (2 * sqrt(mass * stiffness)).

The documentation, however, doesn’t reveal the way the values are picked.

The thing is, when you take more control over the curve and use

UISpringTimingParameters.init(mass: CGFloat, stiffness: CGFloat, damping: CGFloat, initialVelocity: CGVector)
animations ignore their duration and, apparently, run as long as a string with such a curve would oscillate in a real-time ideal physics model (the time axis isn’t mapped). The animation doesn’t run forever though, it stops once the amplitude is too small for the screen to display (checked with a completion).

You can paste and run this code in an Xcode playground for visualization:

import UIKit

import PlaygroundSupport

let v = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 500, height: 500))

let v1 = UIView()

v.backgroundColor = .white

v1.backgroundColor = .blue
v1.translatesAutoresizingMaskIntoConstraints = false

v.addSubview(v1)

v1.heightAnchor.constraint(equalTo: v1.widthAnchor).isActive = true
v1.widthAnchor.constraint(equalToConstant: 90).isActive = true
v1.bottomAnchor.constraint(equalTo: v.bottomAnchor).isActive = true
v1.centerXAnchor.constraint(equalTo: v.centerXAnchor).isActive = true

let string1 = UISpringTimingParameters.init(mass: 1, stiffness: 16, damping: 0.1 * 8, initialVelocity: CGVector.zero)

let animator1 = UIViewPropertyAnimator.init(duration: 1.0, timingParameters: string1)
animator1.addAnimations {

    v1.transform = CGAffineTransform.init(translationX: 0, y: -220)
}
animator1.startAnimation()

PlaygroundPage.current.liveView = v

Hi @anthonylatsis,

This is another one that really deserves to be in a Radar. The UIKit team may not be lurking here on the Swift language forums for bug reports. :)

Sure, makes sense.

Looks like I’ll have to point out my own mistake. Apparently, this is deliberately done, though I have no idea why.

After revising the docs on the underlying CASpringAnimation I stumbled upon the settlingDuration property and found it rather suspicious: CASpringAnimation settlingDuration

In case the definition is confusing, it is the estimated time that has to pass for the spring system to be considered at rest.

Apparently it is actually the settlingDuration that determines the animation duration. Which is very misleading in my opinion.

It looks like in the case of initializing UISpringTimingParameters with simply a dampingRatio, the other parameters are picked up in a manner that the settlingDuration approximately equals the duration of the animation .

When only a dampingRatio is specified, as long as it remains constant and the initialVelocity = 0, it is safe to modify the other properties, since mathematically they only determine how stretched then curve is by axis t (this is how the settlingDuration is adjusted to equal the duration).


  • It is important to note that wit the above conditions it doesn’t bother us how stretched the curve is, since we have to start at t = 0 and finish after duration at the point where the amplitude is too small to display (the settlingDuration). So a very stretched curve and a very shrunk curve with equal dampingFactor and initialVelocity = 0 are equal to us. We can just shrink or stretch it to match the duration and settlingDuration. I hope this is understandable.

However, when you specify all the parameters yourself, the settlingDuration can far exceed or fall behind the duration. For some reason, the curve isn’t stretched or shrunk in time as it could be for the settlingVelocity to match the duration.

To Everyone

This isn’t a bug as it appears, but isn’t this messed up? What are these mysterious cases when we need an animation that ignores its duration and instead lasts for exactly the “estimated time required for the spring system to be considered at rest” in a real-time space? Can’t we be and don’t we have to be strict about the duration of an animation when we can? I can simply assign duration = settlingVelocity for the same effect…

@Tony_Parker will still report this, but as a suggestion.

Well, you’re explicitly specifying two things that are in conflict with each other (the physical parameters and the animation duration) so it has to pick one of them, and in this case I guess it’s not the one that you expected. It’s inherently ambiguous though, so I think if they were picking the duration instead then people would be asking the opposite question about why the animation isn’t reflecting the physical parameters they set.

The iOS spring animation APIs are known to be a bit odd. Here’s an interesting read if you’re curious for a bit more information.

I will try to clarify myself, though it will be obvious in some places

The animation duration and a function don’t conflict in a way you have to choose either of them and lose the other. Time can be mapped to suit you animation. The behavior of a function and its properties don’t change if time is linearly mapped. It’s simply a matter of speed.

Any basic time-based animation has a timing function or an array of them.

A timing function defines how an animated property changes from startValue to endValue throughout the animation duration. It is simply a function f(t) with the conditions f(0) = startValue, f(duration) = endValue that defines the pacing of your animation, where duration is the duration of your animation. Not any other value, obviously.

A spring animation is an animation with a timing function of a damped oscillating spring. It has nothing to do with real-time physics at this point, although the function indeed possesses the properties of a spring. The fact that a damped oscillating spring is a physics model doesn’t mean the animation has to behave as if it is a physics body. It is the curve that matters. The function, and how it behaves. The dependence of progress on time.
The fact that a particular timing function is closely related to a physics model shouldn’t matter whatsoever.

Whether or not the object behaves as a real-time physics model is up to you to configure.

Imagine if easeInOut would be somehow related to physics, and animations because of this would ignore their duration and act as physics bodies instead of scaling the function to match the duration property, which actually is supposed to be the duration of your animation. You can easily set it to the appropriate value if you want a real-time physics model motion.

Concerning the spring curve and why the duration is approximate:
The spring curve is a harmonic function. It will oscillate forever in an ideal physics model (0 < dampingRatio < 1). This is why the curve is cropped from the right side at the moment t when the amplitude is too small for the screen to display. Given a dampingRatio = damping / (2 * sqrt(mass * stiffness)), we have to pick mass, stiffness and damping so that we don’t break the relation and settlingDuration ~ duration. This is why we approximate duration. We can’t perfectly (well, float-presice) match duration. And that is OK, but the animation duration absolutely has to at least approximate duration. Because it is the animation duration.

I’ve seen that article a couple of times. Personally I see nothing odd in the plane old animate method with spring parameters. Actually, the fact that you can set .curveEaseWhatever as an option is a mess. A bit long and outdated too. But that’s pretty much it. The fact that you had to calculate the settlingDuration back then if you wanted the animation to look natural is more like an imperfection (already fixed) rather than an oddity.

I see three solutions:

  • 1 [ There is still something to be done with the duration property in CASpringAnimation(unless duration becomes read-only specifically for CASpringAnimation). ]
    Do not add a duration parameter to an initializer that uses spring parameters (except dampingFactor, when the curve can be adapted to your duration) and document the fact that the animation duration will be equal to the springs’ settlingDuration, which should be gettable from the timingParameters provider object. The duration hence can only be passed at initialization as is read-only.

  • 2 Always respect the animation duration by scaling the curve in time or picking the appropriate parameters when acceptable. The settlingDuration should be gettable, as well as all the other parameters after initialization. Right now I can’t read any parameters apart from the initialVelocity in a UISpringTimingParameters object.

  • 3 Respect the duration when using CASpringAnimation, make the duration optional (split methods) when providing spring parameters in UIView.animate of UIPropertyAnimation. Being provided it is respected, otherwise it is set to the settlingDuration, which as well as in 1 and 2 should be gettable.