TechnologiesSwiftUINavigation

EmptyMatchedTransitionSourceConfiguration struct

iOSmacOStvOSwatchOSvisionOSiOS 18.0+✓ renders

An unstyled matched transition source configuration.

How it works

EmptyMatchedTransitionSourceConfiguration is the default configuration type SwiftUI uses for a view marked as the source of a matched transition. When you tag a view with matchedTransitionSource(id:in:), the framework records that view's geometry and appearance as the starting point for a matched geometry effect — most commonly a zoom navigation transition — and an empty configuration is what it applies when you don't supply any custom styling. Reach for it implicitly whenever you want a view to act as the visual anchor a presented destination grows out of, without altering how that source is captured.

  1. Establish a shared namespace with @Namespace

    A matched transition needs a namespace so SwiftUI can pair the source view with its destination across the presentation boundary. Declare it once with the @Namespace property wrapper — here namespace — and hand the same value to both the source and the destination so they resolve to the same transition group.

  2. Tag the origin view with matchedTransitionSource(id:in:)

    The matchedTransitionSource(id:in:) modifier is what enrolls a view as a matched transition source, and its default configuration is EmptyMatchedTransitionSourceConfiguration. Applied to the RoundedRectangle with id: "hero" and in: namespace, it records that rectangle's frame and content as the anchor the destination will animate from.

  3. Match the identifier on the destination transition

    The configuration only takes effect when a destination claims the same source. The navigationDestination(isPresented:) content uses .navigationTransition(.zoom(sourceID: "hero", in: namespace)), whose sourceID and namespace match the source's id and in: — so the presented view zooms out of the tagged rectangle rather than appearing with a generic push.

  4. Drive the presentation that triggers the transition

    Because EmptyMatchedTransitionSourceConfiguration carries no styling of its own, the captured source is used as-is; the transition fires when the destination is presented. Toggling showDetail from the onTapGesture flips the isPresented binding, and SwiftUI plays the matched zoom using the empty configuration's recorded geometry.

Try it — Change sourceID: "hero" in the .navigationTransition(.zoom(...)) call to a different string and observe the destination fall back to a plain push, since no source matches the empty configuration's recorded id.

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.

EmptyMatchedTransitionSourceConfiguration.swift
struct EmptyMatchedTransitionSourceConfigurationDemo: View {
    @Namespace private var namespace
    @State private var showDetail = false

    var body: some View {
        NavigationStack {
            VStack(spacing: 16) {
                RoundedRectangle(cornerRadius: 16)
                    .fill(.blue.gradient)
                    .frame(width: 120, height: 120)
                    .matchedTransitionSource(id: "hero", in: namespace)
                    .overlay(Text("Tap").foregroundStyle(.white).bold())
                    .onTapGesture { showDetail = true }

                Text("Zoom Transition")
                    .font(.headline)
            }
            .padding()
            .navigationDestination(isPresented: $showDetail) {
                RoundedRectangle(cornerRadius: 16)
                    .fill(.blue.gradient)
                    .navigationTransition(.zoom(sourceID: "hero", in: namespace))
                    .overlay(Text("Detail").foregroundStyle(.white).bold())
                    .padding()
            }
        }
    }
}
Live preview
Tap Zoom Transition 9:41
Tap Zoom Transition 9:41
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →