I have to announce to my Medium Family. I am suffering from Content Creator paralysis syndrome. (This typically occurs after reading every one story about making money on multiple platforms). This is…
Spring animations first became part of the public API back in iOS 7, but the damping-duration-interface from back then was very unnatural. It is still around today and we’ll discuss why it is problematic in a bit. Things got a lot better with the addition of
UIViewPropertyAnimator in iOS 10, but it is still harder to craft animations that feel natural than it should be.
So what are spring animations, how do they work, and how can we craft animations that feel just right?
At the time of writing overdamped springs are not supported by UIKit. Besides, I don’t know where you’d ever want to use an overdamped spring animation — if you need something bouncy, you’d go with an underdamped spring; if you don’t want it to bounce, you’d typically want the animation to settle as quickly as possible and use a critically damped spring.
With a trained eye you can see that with parameters
αc, the differential equation from above has the same solution
s(t) for any
α > 0. This means that a spring’s characteristics are uniquely determined by just two parameters: We could assume a unit mass and tweak only the spring’s stiffness and its damping coefficient.
Unfortunately, UIKit does not currently allow us to construct a spring animation using these parameters. While we can create spring timing parameters using some damping ratio, we cannot specify the frequency response. In fact, there is an infinite number of springs with a given damping ratio that all behave differently! The spring’s underlying physical properties are only determined when we create a
UIViewPropertyAnimator with a fixed duration, but that duration is the duration of the entire animation, the duration until a small enough amplitude is reached — a value not very meaningful for design work either. The whole point of using spring animations is having an animation that feels natural, with the spring parameters you choose — not a spring animation that is, say, critically damped and whose displacement from equilibrium becomes negligible (whatever that may mean…) after precisely
0.75 seconds. The latter will likely feel a little off.
If you instead create spring timing parameters with a mass, damping coefficient and stiffness, the duration you pass to a
UIViewPropertyAnimator is ignored, as it should, because the three parameters alone uniquely identify the spring’s equation of movement, and the animation should run until the amplitude becomes negligible.
So if you want to craft a spring animation that feels natural, don’t explicitly specify the animation’s duration. Instead, create timing parameters that completely specify the spring’s underlying parameters and let the animator derive its duration from those parameters. The following snippet shows how to convert damping coefficient and frequency response to mass, stiffness, and viscous damping coefficient:
Animations interpolate between their from- and to-values using a so-called timing function
f(t) according to the equation
value(t) = start + f(t)·(end — start). This is done regardless of how big or small the distance between the from- and to-value is. For a linear animation the timing function would have the form
f(t) = t / duration, for example. But we can use just about any
f(t), including one motivated by the motion of a spring, as long as
f(0) = 0 and
f(duration) = 1, i.e. the animations starts at the from-value and somehow transitions to the to-value.
This hints at the key difference between physical springs and animations already: the equation of motion for a spring deals with absolute units, say meters. Timing functions, on the other hand, are normalized to the unit interval with their codomain being unitless, indicating the fraction of the animation that has completed. The spring’s velocity is still the derivative of its displacement, but in an animation context this, too, becomes a relative quantity: the fraction of the animation that is completed per second. Therefore a relative initial velocity of
1 corresponds to an absolute initial velocity that covers the entire animation distance in one second. Note that evaluating the normalized spring timing function and using its value to interpolate the from- and to-value yields the same result as evaluating the spring’s equation of motion with appropriately scaled initial position and velocity.
You may wonder what happens if we are already at the target value. We’d divide by zero, resulting in an infinite relative initial velocity, regardless of the absolute initial velocity! The problem is that when the from- and to-value are identical, any inter- or extrapolated value will be, too, and we can’t possibly compute a reasonable relative initial velocity for the spring. In order to preserve the absolute initial velocity, we must first displace the from-value a tiny bit so that we can map the values to the unit interval bijectively.
The smaller we make the manual adjustment, the less noticeable it becomes, and the better the timing function matches the motion of a spring with non-zero initial velocity and no initial displacement from equilibrium. For very small manual adjustments we get relative displacements and velocities of surprisingly high magnitude. Keep in mind though that these values must be interpreted relative to the distance between the animation’s endpoints, which we chose to be very small. In the figure above, the timing function peaks at a relative displacement from equilbrium of roughly
15 which translates to an absolute displacement of
7.5, as we chose the distance between from- and to-value to be
0.5. This matches the motion of the spring which peaks at
The initial velocity of a spring is a scalar value, not a vector. However,
UISpringTimingParameters makes us to specify the initial velocity as a vector. What is going on here?
The reason we get to specify a vector is to make it easier to animate two-dimensional properties like a view’s position, where the initial velocity might be different along the
y axes. This really just creates two spring animations under the hood, one for the
x position and one for the
y position, with potentially different initial velocities and a shared completion handler.
When animating one-dimensional properties, the
y component of the initial velocity is completely ignored. The documentation states that for 1D-properties, the vector’s magnitude is used as the animation’s initial velocity. This is plain wrong. Besides, this would prevent us from having negative initial velocities.
I have found a view’s position (
center) to be the only property where an initial
y velocity has an effect. When animating a view’s size, UIKit does not distinguish between width and height and uses the
x component for both. This is also the case when animating a view’s transform, even if it is only a translation.
Having read the article on the front page of Inside Housing on the 8th of June asking how prevalent sexual harassment is within the sector and that being followed up with more statistics this week…
BDD is a frequently misunderstood concept. Too often, the term BDD is used to refer to integration tests which happen to be running through Behat. Many developers find that their Behat tests add…
This is slightly different content than I usually post, firstly I don’t usually post poetry and secondly, I used ChatGPT prompts to make it. It’s a test. Of many things. I’ll let you know the results…