Skip to main content

How to create zoom animations between views

About 3 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to create zoom animations between views 관련

SwiftUI by Example

Back to Home

How to create zoom animations between views | SwiftUI by Example

How to create zoom animations between views

Updated for Xcode 16

New in iOS 18

SwiftUI provides a zoom transition effect that can be used when pushing to a new view with a NavigationStack, and also used when pushing to a sheet or a full-screen cover presentation. It's works similarly to a matched geometry effect, except it works across presentations where matched geometry effect fails.

Using it takes three steps:

  1. Declare a new animation namespace using @Namespace
  2. Use the matchedTransitionSource() modifier on whatever source view is triggering the transition.
  3. Use the navigationTransition() modifier on whatever destination view you're navigating to.

Both modifiers must be given a stable view ID that is being transitioned, just like matched geometry effects.

Let's start with a simple example, animating to a sheet where all the code is inside a single view.

First, we'd define some data to work with:

struct Icon: Identifiable {
    var id: String
    var color: Color
}

And now we can define a view that shows lots of icons, zooming one up when tapped:

struct ContentView: View {
    let icons = [
        Icon(id: "figure.badminton", color: .red),
        Icon(id: "figure.fencing", color: .orange),
        Icon(id: "figure.gymnastics", color: .green),
        Icon(id: "figure.indoor.cycle", color: .blue),
        Icon(id: "figure.outdoor.cycle", color: .purple),
        Icon(id: "figure.rower", color: .indigo),
    ]

    @Namespace var animation
    @State private var selected: Icon?

    var body: some View {
        LazyVGrid(columns: [.init(.adaptive(minimum: 100, maximum: 300))]) {
            ForEach(icons) { icon in
                Button {
                    selected = icon
                } label: {
                    Image(systemName: icon.id)
                }
                .foregroundStyle(icon.color.gradient)
                .font(.system(size: 100))
                .matchedTransitionSource(id: icon.id, in: animation)
            }
        }
        .sheet(item: $selected) { icon in
            Image(systemName: icon.id)
                .font(.system(size: 300))
                .foregroundStyle(icon.color.gradient)
                .navigationTransition(.zoom(sourceID: icon.id, in: animation))
        }
    }
}

That works, but in practice you're going to want to split the source and destination views more cleanly, like this:

struct ContentView: View {
    let icons = [
        Icon(id: "figure.badminton", color: .red),
        Icon(id: "figure.fencing", color: .orange),
        Icon(id: "figure.gymnastics", color: .green),
        Icon(id: "figure.indoor.cycle", color: .blue),
        Icon(id: "figure.outdoor.cycle", color: .purple),
        Icon(id: "figure.rower", color: .indigo),
    ]

    @Namespace var animation
    @State private var selected: Icon?

    var body: some View {
        LazyVGrid(columns: [.init(.adaptive(minimum: 100, maximum: 300))]) {
            ForEach(icons) { icon in
                Button {
                    selected = icon
                } label: {
                    Image(systemName: icon.id)
                }
                .foregroundStyle(icon.color.gradient)
                .font(.system(size: 100))
                .matchedTransitionSource(id: icon.id, in: animation)
            }
        }
        .sheet(item: $selected) { icon in
            DestinationView(icon: icon, animation: animation)
        }
    }
}

struct DestinationView: View {
    var icon: Icon
    var animation: Namespace.ID

    var body: some View {
        Image(systemName: icon.id)
            .font(.system(size: 300))
            .foregroundStyle(icon.color.gradient)
            .navigationTransition(.zoom(sourceID: icon.id, in: animation))
    }
}
Similar solutions…
How to handle pinch to zoom for views | SwiftUI by Example

How to handle pinch to zoom for views
How to override animations with transactions | SwiftUI by Example

How to override animations with transactions
How to create basic animations | SwiftUI by Example

How to create basic animations
How to create custom text effects and animations | SwiftUI by Example

How to create custom text effects and animations
How to create multi-step animations using phase animators | SwiftUI by Example

How to create multi-step animations using phase animators

이찬희 (MarkiiimarK)
Never Stop Learning.