Building Engaging User Interfaces with SwiftUI

Mar 12 2025 · Swift 5.9, iOS 17.0, XCode 15.0

Lesson 01: Mastering SwiftUI Animations

Multiple Animations & Animating Paths

Episode complete

Play next episode

Next
Transcript

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.

See forum comments
Cinema mode Download course materials from Github
Previous: Exploring Animation Types Next: Using View Transitions