Starting with SwiftUI Animations
First, open the starter project for this lesson. You’ll see an app showing flight information for a fictional airport. Tapping the Flight Status button displays the flight status board, which provides flyers with the time and gate for the flight’s departure or arrival.
Open FlightInfoPanel.swift in the FlightDetails group and look for the following code:
Button {
showTerminal.toggle()
} label: {
HStack {
Text(showTerminal ? "Hide Terminal Map" : "Show Terminal Map")
Spacer()
Image(systemName: "airplane.circle")
.imageScale(.large)
.padding(10)
.rotationEffect(.degrees(showTerminal ? 90 : -90))
}
}
This code creates a Button
that toggles the showTerminal
state variable when the user taps the button. The code uses the value of the variable so the text shows the action the next tap will cause. It also alternates the rotationEffect
angle between two values based on the state of the showTerminal
variable.
Now, find the immediately following code:
if showTerminal {
TerminalMapView(flight: flight)
}
This code toggles showing the terminal map based on the showTerminal
state variable. Run the app, tap a flight, and you’ll see the rotation flips between the two states with each tap.
These two components provide a framework for the basics of SwiftUI animation. In fact, SwiftUI already animates the change of the aircraft angle for you.
Note: Animations and transitions sometimes render incorrectly in the preview. If you don’t see what you expect in this lesson, run the app in the simulator or a device instead of relying on the preview.
The default animation is a type of eased animation referred to as easeInOut
. This animation looks good in almost all cases, so it’s a good choice if you have no other strong preference. You’ll examine the different eased animations later in this lesson. To get more control over the animation in SwiftUI, you simply provide the type of animation and let SwiftUI handle the interpolation for you. After the .rotationEffect(_:anchor:)
modifier, add the following code:
.animation(.default, value: showTerminal)
This explicitly applies the default animation to the state change. In addition to the type of animation, you specify the variable that triggers the animation.
Run the app, tap the Flight Status button, and then tap any flight on the list. You’ll see the terminal map hidden by default. Tap the text or the airplane icon to show the map. You’ll see the icon rotate between the up and down positions as you toggle the view.
The rotation from -90 to 90 degrees acts as a state change, and you’ve told SwiftUI to animate this state change by adding the .animation(_:value:)
modifier. The animation applies only to the Image element’s rotation and no other views on the page, and it activates only when showTerminal
changes.
Because SwiftUI iterates between the values when animating, the angles matter when you create an animation. If you changed the second angle to 270 degrees, the image will rotate in the opposite direction before ending in the same position. This occurs because larger angles rotate clockwise around the origin and negative angles rotate counterclockwise. Because showTerminal
begins as false
, the second value will be the initial state. Earlier, the icon turned clockwise when moving from upward to pointing downward. Now, it rotates counterclockwise from 270 to 90 degrees.
You’re not limited to the 0 to 360 degrees range of a single rotation. If you changed the 270
to 630
(270 plus a 360 full rotation), you’d see that it spins one and a half rotations before stopping. The rotation would also speed up to cover the larger range in the same amount of time.
Before continuing, change the rotation to:
.rotationEffect(.degrees(showTerminal ? 90 : 270))
Animation Types
You can use any of the built-in animation types by specifying it in place of the default animation. After the .rotationEffect(_:anchor:)
modifier, add the following code:
.animation(.linear(duration: 1.0), value: showTerminal)
Now, instead of the eased animation, you’ll see a linear animation. This animation supports a parameter telling SwiftUI how long the animation should take. Here, you set the animation to last for one second. A linear animation moves at a constant rate from the original state to the final state. If you graphed the change vertically against time horizontally, the transition would look like this:
You’ll see several graphs like this in this segment. The graph shows the change of position as time passes. The slope at any point represents the speed of the animation. A steeper line indicates faster animation. The lack of change shown in this graph reflects the constant speed of the animation.
Unfortunately, the default
animation did not include a duration
parameter. To achieve the same effect, you could use the following code:
.animation(.default.speed(0.33), value: showTerminal)
The speed(_:)
method is one of several you can apply to any animation. It adjusts the animation’s speed — in this case slowing it because the value is less than 1. If you use a value greater than 1, the animation speed increases.
Run the app and go to the details for a flight. Although not identical, you’ll see the animation runs at a speed similar to the linear one. Without changing the speed, the rotation of the plane would finish in one-third the time.
Eased Animations
Eased animations might be the most common in apps and are the default type in SwiftUI. They generally look more natural since an object can’t instantaneously change position and speed in the real world. An eased animation applies an acceleration, a deceleration, or both at one or both of the animation’s endpoints. The animation reflects the acceleration or deceleration of real-world movement.
The default animation is the equivalent of the easeInOut
type. This animation applies acceleration at its beginning and deceleration at its conclusion.
If you graphed the movement in this animation against time, the graph looks like this:
Here, you see the animation speeds up as the line gets steeper before slowing near the end. You can get more control using it directly. Change the animation of the airplane icon to:
.animation(.easeInOut(duration: 1.0), value: showTerminal)
Eased animations have a short default time of 0.35 seconds; you can specify a longer length with the duration:
parameter to match the earlier animation.
Note: If you have trouble seeing animations or the differences between animation options, you can turn on Debug ▸ Slow Animations in the simulator to reduce the animation speed significantly. Be sure to turn it off when you have finished.
Now, change the animation to:
.animation(.easeOut(duration: 1.0), value: showTerminal)
Run the app and toggle the terminal map. You’ll see the rotation starts quickly and slows shortly before stopping.
Graphing the movement in this animation against time looks like this:
In addition to easeOut
, you can also specify easeIn
, which starts slowly at the animation’s beginning and then accelerates.
If you need fine control over the animation curve’s shape, you can use the timingCurve(_:_:_:_)
method. SwiftUI uses a Bézier curve for easing animations. This method lets you define the control points for that curve in a range of 0 to 1. The curve’s shape reflects the specified control points.
As with the other graphs, the slope of the curve indicates the animation speed. A Bézier curve is defined by four points. Here, two are locked to the bottom-left and top-right of the graph. The two control points (c0 and c1) control the initial and final speed of the animation. The speed of the initial animation (the slope of the curve) is parallel to a line from the bottom-left corner to the first control point. Similarly, the final speed of the animation is parallel to the line from the top-right to the second control point. SwiftUI handles smoothing out these points into the full curve. This example curve shows an animation that starts slow, increases speed through the middle, and then slows again at the end. Moving the control points allows you to customize the change.
Although eased animations are the most commonly used animations, you’ll find another type of animation useful. Eased animations always transition between the start and end states in a single direction. They do not extend beyond the start or end state. The other SwiftUI animations category lets you add a bit of bounce at the end of the state change. The physical model for this type of animation gives it its name: a spring.
A physical spring resists stretching and compression — the greater the spring’s stretch or compression, the more resistance the spring provides. Imagine you attach a weight at one end of a spring. Attach the spring’s other end to a fixed point and let the spring drop vertically with the weight at the bottom. It will bounce several times before coming to a stop. If you were to graph the weight’s position as time passes, it would look like this:
This graph illustrates the position of an oscillating system over time, demonstrating damped simple harmonic motion. Initially, the system starts from an extreme position, with gravity pulling the weight down until the restoring force of the stretched spring overcomes gravity and moves the weight back up. Because of friction and other outside forces, the system loses energy each time through the cycle, moving a shorter distance than it did the previous cycle. This reduction makes the system damped. These accumulated losses add up, and eventually, the weight will stop motionless at the equilibrium point.
In the next section, you’ll experiment with the different types of animations.