Now that you have an understanding of SwiftUI animations, you’ll apply animation to other places in the app. Open TerminalStoresView.swift in the FlightDetails group. This view displays the stores in each terminal. In this segment, you’ll add some animation to these shapes when they appear. First, add a state variable to the struct below the flight
property:
@State private var showStores = 0.0
Now, find the declaration of xOffset
in the ForEach
loop and change it to:
let xOffset =
Double(index) * storeSpacing * direction * showStores + firstStoreOffset
You added a multiplication by the showStores
state property. This calculation determines the store’s horizontal position. By setting showStores
to 0, the stores all initially appear at the firstStoreOffset
. By setting showStores
to 1, they appear in the previous location. Changing the value of a variable like this creates a state change you can animate.
At the end of the GeometryReader
, add the following code:
.onAppear {
withAnimation(.easeInOut) {
showStores = 1.0
}
}
Code inside onAppear(perform:)
executes when the attached view appears on the device. You create an explicit animation using a default ease-in-out animation. In the closure, you change the value of showStores
to 1.0. The combination tells SwiftUI to apply an ease-in-out animation to anything that the change to showStores
affects. This includes the offsets.
Now when you run the app, tap Search Flights, choose any flight, and tap Show Terminal Map. You see the map appear and the store shapes slide into place.
The delay()
method allows you to specify a time in seconds to pause before the animation occurs. You can also use it to allow animations to chain together and provide a sense of progress or motion.
In TerminalStoresView.swift, change showStores
to:
@State private var showStores = false
This changes showStores
to a Boolean. Change the definition of xOffset
in the ForEach
loop back to:
something with offset(x:y:)
This code removes the state change from the calculation. Instead, you’ll change the state directly in the offset(x:y:)
modifier. Change the RoundedRectangle
offset
modifier to:
.offset(
x: showStores ?
xOffset :
firstStoreOffset - direction * width,
y: height * 0.4
)
You also need to change the code in onAppear(perform:)
to:
.onAppear {
showStores = true
}
This both toggles showStores
to true when the view appears and removes the explicit animation. Add the following animation after the offset on the rectangle:
.animation(.easeOut.delay(Double(index) * 0.35), value: showStores)
You apply a different animation to each iteration through the ForEach
loop. This code now uses the same index
property used to set the store’s position. For each greater index, you delay the animation by 0.35 seconds. As the default ease-out animation takes 0.35 seconds, this delay lasts long enough so it ends just as the previous animation completes.
You see the result of the delayed animations as the stores snap into place one at a time. Note the choice of delay so each just finishes before the next starts.
Making a Path
As a final example of how you can animate anything you can move into a state change, you’ll add an animation to the path. A SwiftUI Shape
has a method trim(from:to:)
that trims a shape to a fractional portion based on its representation as a path. For a shape implemented as a path, the method provides a quick way to draw only a portion of the path.
First, go to GatePathView.swift and add the following code after the current GatePathView
struct:
struct WalkPath: Shape {
var points: [CGPoint]
func path(in rect: CGRect) -> Path {
return Path { path in
guard points.count > 1 else { return }
path.addLines(points)
}
}
}
This struct implements a custom Shape
view that takes an array of points and creates a path like the existing view. Now, add a new state variable after the flight
parameter at the top of the struct:
@State private var showPath = false
Then, add a new animation property after the showPath
property:
var walkingAnimation: Animation {
.linear(duration: 3.0)
.repeatForever(autoreverses: false)
}
This code creates a linear animation lasting three seconds. The repeatForever(autoreverses:)
method sets the animation to repeat when it finishes. Setting autoreverses
to false
means the animation restarts each time instead of rewinding backward before restarting.
Change the closure for the GeometryReader
in the body to use the new shape instead of drawing the path directly:
WalkPath(points: gatePath(proxy))
.trim(to: showPath ? 1.0 : 0.0)
.stroke(lineWidth: 3.0)
.animation(walkingAnimation, value: showPath)
The added trim(from:to:)
method contains the state change. You also attach the animation to the view telling SwiftUI to animate the state change.
Finally, add the following code at the end of the view after the GeometryReader
:
.onAppear {
showPath = true
}
As earlier, you use the onAppear(perform:)
modifier to change the state when the view appears.
Run the app and show any terminal map. You see the line trace out the path to the gate and repeat every three seconds.
Now that you understand basic animations, you’ll learn about transitions — a special animation applied to views when they appear and disappear — in the next segment.