SwiftUI: Animation

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

Part 1: Beginning with SwiftUI Animation

04. Combine Animations

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: 03. Animation Options Next episode: 05. Challenge: Rotation
Transcript: 04. Combine Animations

In this episode, you’ll work in a project called Savanna Tours: an app that offers tours in the African savanna.

In the Canvas Editor, you can see each tour’s details including a title, a short description, a large photo, and a list of the tour’s milestones — which is currently the only interactive portion of the UI. You’re going to add interactivity, and animation, to this thumbnail.

Your app’s users will be able to tap on it, and see a larger version of it: presented, and dismissed, with a neat animation.

First, as is often the case in SwiftUI, you’ll need a property to hold some of your state. Namely, whether the thumbnail is zoomed or not. A simple Boolean will suffice for that purpose.

struct ContentView : View {
  @State var zoomed = false

  var body: some View {

To toggle between zoomed-in and -out states, add an “on tap gesture” modifier to the thumbnail, right below its shadow.

            .shadow(radius: 10)
            .onTapGesture { zoomed.toggle() }
        }

Now, each time you tap on the thumbnail view, the zoomed state will alternate between false and true. Let’s put that new state to work, in this scale effect modifier.

Instead of a one third scale, let’s change it to four thirds, when zoomed.

            )
            .scaleEffect((zoomed ? 4 : 1) / 3)
            .shadow(radius: 10)

Go ahead and click the thumbnail in the preview… and oh, no! What happened?! It disappeared, because this hard-coded position of (600, 50)… only yields a useful result when the thumbnail is scaled down. Once the image is enlarged, it’s repositioned off of the screen.

So instead, when zoomed in, let’s utilize the geometry proxy from the Geometry Reader above.

x: zoomed ? geometry 600,

You can use its frame method to get a rectangle that represents the parent view of the image.

x: zoomed ? geometry.frame(in: .local) 600,

And then, use its midX property.

              x: zoomed ? geometry.frame(in: .local).midX : 600,

Now, tap it! Good positioning. Now, we just need to animate it. Which you can do, just like you’ve already learned.

            .shadow(radius: 10)
            .animation(.default, value: zoomed)
            .onTapGesture { zoomed.toggle() }

Click a few times on the thumbnail and observe the animation. Notice how SwiftUI automatically interpolates the multiple animations you’re running on the Image:

The first animation scales the thumb way up, which pushes its location to the right. If you weren’t changing its position as well, it would’ve animated out of the screen.

The second animation animates the thumb’s position leftwards to keep it inside the screen while it scales up.

SwiftUI interpolates the changes of both animations. The final result is a neatly curving motion that starts rightwards but turns around and ends to the left of its starting point. That builds a very nice zoom effect!

But to make it even more slick, replace the default animation with a spring-driven one:

            .animation(.spring(), value: zoomed)

The harmonic oscillation gives it just a little more life. Now, tap repeatedly in quick succession on the image.

Interrupting the current animation never gives you a “broken” layout. All animations in SwiftUI are interruptible and reversible by default: you get this for free out of the box.

Next, you’re going to add another animation, to yet another view, on your screen. You can drive as many animations as you want, from a single state change. The VStack above, for the tour title, already has an animation modifier.

        )
        .animation(.default)

        GeometryReader { geometry in

It just wasn’t doing anything, because no changes were being applied to it. Let’s move it to the right, during the zoom.

        🟩.offset(
          x: zoomed ? 500 : 30,
          y: -30
        )
        .animation(.default, value: zoomed)

Now, tapping the thumbnail will now hide the tour title underneath itself. Let’s experiment with another animated modifier. At the moment, the Image has a corner radius large enough to make it look like a circle.

When zoomed, use a smaller one. Like 40.

          Image("thumb")
            .clipShape(
              RoundedRectangle(cornerRadius: zoomed ? 40 : 500)
            )
            .overlay(

Now, tapping the thumbnail gives it a different shape. Snazzy. And of course, all its changes are nicely animated with a fluid spring animation.

SwiftUI makes drawing and animating shapes really easy, because you apply the same basic principles to shapes, and other views. You don’t need to differentiate between them. They’re both first-class citizens of your UI.

And while you’ve experienced a bit of what you can do with animating color already, there’s a lot more available to you in that department — such as animating the modifiers that affect color. Like this saturation, here. Clear out the transparent white when zoomed.

            Circle()
                .fill(
                  zoomed
                    ? Color.clear
                    : Color(white: 1, opacity: 0.4)
                )
                .scaleEffect(0.8)

The overlaid inner circle, which is 80 percent of the size of the image (and helps make it look like more of a badge), is now animated to be invisible.

To go with that, restore the image’s saturation, at the same time.

                .scaleEffect(0.8)
            )
            .saturation(zoomed ? 1 : 0)
            .position(

Aaaand congratulations – you’ve built a beautiful animation by using a single state property and by leveraging SwiftUI’s animation super-powers!