SwiftUI: Animation

Mar 29 2022 · Swift 5.5, iOS 15, Xcode 13

Part 1: Beginning with SwiftUI Animation

02. Animation

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 01. Introduction Next episode: 03. Animation Options
Transcript: 02. Animation

As a refresher: when using SwiftUI, you describe your user interface declaratively and leave the rendering to the framework. Each of the views you declare for your UI (like text labels, images or shapes) adheres to the View protocol. View requires each view struct to feature a property called body.

Any time you change your data model, SwiftUI asks each of your views for their current body: because they might change according to your latest changes.

It then builds the view hierarchy to render onscreen. In a sense, SwiftUI makes “snapshots” triggered by changes in the data model. Since SwiftUI can identify all views in your view hierarchy, it’s not difficult for it to animate any changes to your views.

If, in the current snapshot of your views, a given view is 100 pixels to the right, it’s a piece of cake for SwiftUI to animate that change.

It works the same way if your view is larger than it used to be…

…or if it’s now purple, where it used to be pink.

SwiftUI keeps track of the state in the previous snapshot. And, if you need it to, it can animate any changes you declare for your views. Let’s try some animations in Xcode and see how it works in practice.

Open the starter project provided for this episode. There’s a fine green circle on the right, which isn’t animated in any way. Yet.

See this green color? Let’s extract it into a state property. To do that, double-click on this ellipsis, to expand AnimationData. I’ve used this to supply you with an array of colors and offsets. Let’s start off by storing the first of these.

  }

  @State private var animationData = AnimationData.array[0]

  var body: some View {

…and, using that state.

.foregroundColor(animationData.color)

Marking properties as “State” will trigger a new snapshot of your view each time they’re modified. To update your user interface, you don’t adjust your views directly; instead, you modify state properties. That triggers a new render of your UI automatically for you. This should be review for you at this point.

Now, each time you modify color, SwiftUI will create a new snapshot of the circle with its new foreground tint. To see that in action, we’ll cycle through the animation data array.

And we’ll start that process off when the view appears.

      .foregroundColor(animationData.color)
      .onAppear {

      }
  }

Use a for loop, and Enumerate the animation data array.

      .onAppear {
        for (index, data) in AnimationData.array.enumerated()
      }

Except, we won’t need the first one, because that’s what our state starts out as.

      .onAppear {
        for (index, data) in AnimationData.array.enumerated().dropFirst() {

        }
      }

To perform some work that will happen over time, we’re going to need to use a dispatch queue. Don’t worry if you’re not familiar with those yet. There’s only one associated with UI, and that’s the “main” one.

        for (index, data) in AnimationData.array.enumerated().dropFirst() {
          DispatchQueue.main
        }

To do something after a certain point in time has just occurred, you can use its “async after” method.

           for (index, data) in AnimationData.array.enumerated().dropFirst() {
          DispatchQueue.main.asyncAfter(deadline: <#T##DispatchTime#>, execute: <#T##() -> Void#>)
        }

We’ll start at “now”…

   deadline: .now(),

…and add some seconds, corresponding with the array index.

   deadline: .now() + .seconds(index),

At that point, set the animationData property accordingly.

             DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(index)) {
            animationData = data
          }

And now, hit the Live Preview button! Every second, your color is changing.

And, that’s sort of an animation, but there’s no value interpolation happening. The colors just change instantaneously. To smooth things out, add the animation modifier.


Hi again! It’s me, Catie. Here’s where the breaking change comes in. You used to be able to throw an animation modifier onto a view like this:

.foregroundColor(animationData.color)
🟩.animation(.easeIn)

And SwiftUI would animate your view anytime the it updated, and so perhaps at times you did not expect. So now, we have this helpful deprecation warning, telling us not to do this anymore.

We need to be a bit more specific and tell SwiftUI when it should animate by specifying a value to watch for changes. The value you pass in here must be Equatable, which animationData is!

.animation(_____, value: animationData)

Now this modifier will trigger an animation when animationData updates. So SwiftUI knows that you want your changes animated, and when, but not how. I’ll give you back to Jessy for that.


We’ll explore the other options in the next episode, but for now, just go with the default.

.animation(.default, value: animationData)

Live Preview now, and you’ll see that this “default” applies some kind of timing curve, crossfading between colors.

In addition to color, an AnimationData instance also comes with an offset. And, being a CGSize, that’s designed to be used directly with a view’s offset modifier.

      .animation(.default, value: animationData)
      🟩.offset(animationData.offset)
      .onAppear {

Let’s just pad this circle so it doesn’t hit the edges…

      .offset(animationData.offset)
      🟩.padding()
      .onAppear {

…and, send your circle on a trip around the screen!

With that, our first SwiftUI animation is complete. As you see here, it’s really simple to combine the animation of multiple properties.

Next, let’s explore the Animation structure, which is behind the magic.