Change the animation for the icon to:
.animation(
.interpolatingSpring(
mass: 1,
stiffness: 100,
damping: 10,
initialVelocity: 0
),
value: showTerminal
)
When you run the app, you see the icon bounces a bit at the end. The icon continues a bit past the destination, slides back, and then bounces around the final position a bit before stopping.
You use four parameters to define a spring animation:
-
mass
: Controls how long the system “bounces”. -
stiffness
: Controls the initial movement’s speed. -
damping
: Controls how rapidly the system slows and stops. -
initialVelocity
: Gives an extra initial motion.
To see how these parameters affect the animation, change each one at a time.
First, increase the mass to 100
and view the animation.
Next, change the mass to 0.5
and view the animation.
You can see that increasing the mass
causes the animation to last longer and bounce further on each side of the endpoint. A smaller mass stops faster and travels a shorter distance past the endpoints on each bounce.
Next, double the stiffness. Then halve it. You see increasing the stiffness
causes the bounce to move farther past the endpoints but with less effect on the animation’s duration.
The other parameters are more intuitive. Increasing the damping
smooths and ends the animation faster. Increasing the initialVelocity
causes the animation to bounce farther. A negative initialVelocity
can move the animation in the opposite direction until it overcomes the initial velocity.
Unless you’re a physicist, the animation’s physical model doesn’t intuitively map to the results. SwiftUI introduces a more intuitive way to define a spring animation. The underlying model doesn’t change, but you can specify parameters to the model better related to how you want the animation to appear in your app. Change your animation to:
.animation(
.spring(
response: 0.55,
dampingFraction: 0.45,
blendDuration: 0
),
value: showTerminal
)
If you didn’t dampen the animation, the position would begin at the initial value, move to the final value, and then return to the initial value. This loop would complete one oscillation. The response
parameter defines the time to complete that single oscillation if you don’t dampen the system, which is where you set the dampingFraction
to zero. It allows you to tune the animation’s duration.
The dampingFraction
controls how quickly the “springiness” stops. A value of 0 never stops. A value of 1 or more causes the system to stop without oscillation. This overdamped state resembles an eased animation. You usually use a value between 0 and 1, which results in some oscillation before the animation ends. Greater values slow down faster.
The blendDuration
parameter provides a control for blending the length of the transition among different animations. It only comes into use if you change the parameters during animation or combine multiple spring animations. A 0 value turns off blending.
Removing and Combining Animations
In SwiftUI’s initial release, animations could sometimes occur where you didn’t want them. Adding the value
parameter to the animation(_:value:)
addresses much of this problem. There still might be times that you want to apply no animation. You do this by passing a nil
animation type to the animation(_:value:)
method.
Still in FlightInfoPanel.swift, add the following modifier after the .rotationEffect
modifier:
.scaleEffect(showTerminal ? 1.5 : 1.0)
This change adds a scaling of 1.5 times the icon’s original size when showing the terminal map. If you view the animation, you see the button grows in sync with the rotation. An animation affects all state changes that occur on the element where you apply the animation.
Next, add the following code between the rotationEffect()
and scaleEffect()
methods:
.animation(nil, value: showTerminal)
Trigger the animation again. You should see a rapid fade-out/fade-in effect on the rotation, but the size change still shows a spring animation. Again, think of an animation
as affecting all state changes attached to it.
You can combine different animations by using .animation(_:value:)
multiple times. Change the animation on the rotationEffect()
from nil
to:
.animation(.linear(duration: 1), value: showTerminal)
You see the two animations take place simultaneously but produce different effects. The rotation shows a linear animation, while the scaling of the icon shows a spring animation. Also, note that SwiftUI handles the animations’ different durations with no problems.
Explicit Animations
So far, you’ve applied animations only to view elements that changed. This produces an implicit animation, one triggered by a state change. You can also produce explicit animations where you attach the animation to the change.
Remove all .animation(_:value:)
modifiers from the images. Change the action of the button that toggles showing the terminal map to:
withAnimation(
.spring(
response: 0.55,
dampingFraction: 0.45,
blendDuration: 0
)
) {
showTerminal.toggle()
}
You wrap the state change to showTerminal
inside a withAnimation(_:_:)
method. This call uses a spring animation, but you could pass any animation to this function.
Using withAnimation(_:_:)
applies the animation to every visual change that results from the state change in the closure. Between the start of the HStack
and the Text
view inside the button, add the following code:
Image(systemName: "airplane.circle")
.imageScale(.large)
.padding(10)
.rotationEffect(.degrees(showTerminal ? 90 : 270))
Spacer()
Notice the animation happens to both icons. Explicit animations simplify the code when you wish to use a single animation type on multiple changes that result from a state change. Be careful, because SwiftUI applies the animation for all state changes. If another property relied on the value of showTerminal
, the animation would also apply to that property.
Now that you’ve learned the basics of SwiftUI animations, you’ll explore combining multiple animations and animating paths in the next section.