TechnologiesSwiftUINavigation

MatchedTransitionSourceConfiguration protocol

iOSmacOStvOSwatchOSvisionOSiOS 18.0+✓ renders

A configuration that defines the appearance of a matched transition

How it works

MatchedTransitionSourceConfiguration describes the appearance of the source view that anchors a matched geometry navigation transition — most notably the zoom transition. When you mark a view as a transition source, SwiftUI hands you a value of this type so you can shape how that source looks as the animation hands off to the destination, controlling details like corner shape and clipping along the way. Reach for it through the closure form of matchedTransitionSource(id:in:) whenever the default source styling doesn't match the geometry you're animating from, so the morph between the source and its zoomed destination reads as a single continuous element.

  1. Establish a shared namespace with @Namespace

    A matched transition pairs a source and a destination by a common identifier living in the same animation namespace. Declare one with @Namespace private var namespace and pass it to both ends so SwiftUI knows the Button("Open Photo") source and the Color.blue destination are the same element mid-flight.

  2. Mark the source with matchedTransitionSource(id:in:)

    Attach matchedTransitionSource(id: "photo", in: namespace) to the view you're animating away from. This registers that view as the geometric origin of the transition; the id string and the namespace together must match the destination's transition for SwiftUI to connect them.

  3. Style the source through the configuration closure

    The trailing-closure form of matchedTransitionSource yields a MatchedTransitionSourceConfiguration as config. Return a modified configuration from the closure — here config.cornerRadius(16) rounds the source's corners — and SwiftUI carries that styling into the transition so the source's shape lines up with how the zoomed view appears.

  4. Pair it with navigationTransition(.zoom) on the destination

    The configuration only matters when a destination opts into the matched animation. The pushed Color.blue view applies navigationTransition(.zoom(sourceID: "photo", in: namespace)), whose sourceID and namespace resolve back to the configured source, completing the zoom hand-off when showDetail flips to true.

Try it — Bump config.cornerRadius(16) up to config.cornerRadius(40) and watch the source's rounding — and the shape it morphs from during the zoom — change to match.

Example & preview

Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.

MatchedTransitionSourceConfiguration.swift
struct MatchedTransitionSourceConfigurationDemo: View {
    @Namespace private var namespace
    @State private var showDetail = false

    var body: some View {
        NavigationStack {
            Button("Open Photo") {
                showDetail = true
            }
            .matchedTransitionSource(id: "photo", in: namespace) { config in
                config
                    .cornerRadius(16)
            }
            .padding()
            .navigationDestination(isPresented: $showDetail) {
                Color.blue
                    .frame(width: 240, height: 240)
                    .navigationTransition(.zoom(sourceID: "photo", in: namespace))
            }
        }
    }
}
Live preview
Open Photo 9:41
Open Photo 9:41
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →