← All daysDay 24 of 49 · Part 6: Drawing and animation
Day 24

Transitions and matched geometry

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 27

Transitions and matched geometry

A transition describes how a view appears and disappears: .opacity, .slide, .scale, .move(edge:), or a combination. It applies when a view is inserted into or removed from the hierarchy, inside an animation. matchedGeometryEffect links two views in different places so that one appears to move and resize into the other — the "hero" effect. For animation with more than two stages, PhaseAnimator steps through a list of states, and KeyframeAnimator gives each property its own timeline.

Keep the two ideas apart. An animation changes a value on a view that stays. A transition happens when a view enters or leaves.

Exercise

Show a grid of thumbnails. Tapping one expands it into a full-size card, using matchedGeometryEffect with a shared namespace.

After this lesson you should be able to answer
  1. When does a transition run, as opposed to an animation?
  2. What does matchedGeometryEffect need in order to connect two views?
  3. When would you use KeyframeAnimator instead of withAnimation?
Resources
Find the bug
Interview problem · Part 6

The card that fades instead of growing

“Design wants this card to grow smoothly when tapped. Instead it cross-fades between two sizes. Why? And what is the one idea behind the fix?”

struct ExpandingCard: View {
    @State private var isExpanded = false

    var body: some View {
        VStack {
            if isExpanded {
                RoundedRectangle(cornerRadius: 16)
                    .fill(.orange)
                    .frame(height: 300)
            } else {
                RoundedRectangle(cornerRadius: 16)
                    .fill(.orange)
                    .frame(height: 100)
            }
        }
        .onTapGesture {
            withAnimation(.spring) { isExpanded.toggle() }
        }
    }
}

Where this comes from: Senior iOS Interview Preparation (GitHub) · WWDC21: Demystify SwiftUI (2021 — still the reference on identity)

Show the answer

To SwiftUI, the two branches of an if/else are two different views. Toggling removes one and inserts the other, and the default transition for removal and insertion is a fade. Nothing was ever asked to change size.

struct ExpandingCardFixed: View {
    @State private var isExpanded = false

    var body: some View {
        RoundedRectangle(cornerRadius: 16)
            .fill(.orange)
            // one view, one identity; only a value changes
            .frame(height: isExpanded ? 300 : 100)
            .onTapGesture {
                withAnimation(.spring) { isExpanded.toggle() }
            }
    }
}

The one idea: an animation interpolates a value on a view whose identity stays the same. Change the identity and you get a transition instead. If two genuinely different views must appear to turn into one another, that is what matchedGeometryEffect is for.

What interviewers listen for: The sentence "the identity changed, so I got a transition, not an animation." It shows you know why, and not only the fix.