matchedTransitionSource
matchedTransitionSource marks a view as the geometric source of a navigation transition. It allows a view to act as the starting point of a page-level transition animation, such as a zoom (Hero-style) transition.
This API corresponds to SwiftUI’s matchedTransitionSource and is intended only for navigation transitions, not for component-level layout animations.
Typical use cases include:
- Image → Image detail zoom
- Card → Detail page Hero animation
- Avatar → Profile page transition
1. API Definition
1/**
2 * Identifies this view as the source of a navigation transition, such as a zoom transition.
3 * @available iOS 18.0+
4 */
5matchedTransitionSource?: {
6 id: string | number
7 namespace: NamespaceID
8}
2. Core Purpose
The core purpose of matchedTransitionSource is:
To define which exact view should be used as the starting geometry of a navigation transition.
It solves the following problems:
- Eliminates the visual disconnect between a tapped view and the destination page
- Prevents “disappear → new page appears” jump cuts
- Enables spatial continuity between the source element and the destination layout
With this API, the system can:
- Read the source view’s real on-screen frame
- Compute the destination page’s final layout frame
- Animate smoothly between the two
3. Parameter Details
3.1 id — Transition Source Identifier
Meaning:
- Uniquely identifies which view is the transition source
- Must exactly match the destination page’s
navigationTransition.sourceID
Rules:
3.2 namespace — Transition Namespace
Meaning:
- Defines the transition animation scope
- Created and injected by
NamespaceReader
Rules:
- The source view and the destination page must use the exact same namespace
- Different namespaces will never produce a matched transition
- Even if
id is the same, a different namespace disables the animation
4. How matchedTransitionSource Works
A successful navigation zoom transition requires all four conditions to be satisfied:
- A view defines
matchedTransitionSource
- The destination page defines
navigationTransition
navigationTransition.sourceID === matchedTransitionSource.id
- Both sides use the same
namespace
Only when all conditions are met will the system:
-
Capture the source view’s:
-
Capture the destination layout’s final frame
-
Compute:
- Translation path
- Scale ratio
-
Perform the full transition animation
5. Minimal Working Example: Image → Detail Zoom
1<NamespaceReader>
2 {namespace => (
3 <NavigationLink
4 destination={
5 <DetailPage
6 navigationTransition={{
7 type: "zoom",
8 namespace,
9 sourceID: "cover"
10 }}
11 />
12 }
13 >
14 <Image
15 source="cover"
16 frame={{
17 width: 120,
18 height: 160
19 }}
20 matchedTransitionSource={{
21 id: "cover",
22 namespace
23 }}
24 />
25 </NavigationLink>
26 )}
27</NamespaceReader>
Resulting Behavior
- The user taps the image
- Navigation begins
- The destination page does not appear instantly
- Instead, it zooms smoothly from the tapped image’s position and size
6. Card → Detail Hero Transition Example
1<NamespaceReader>
2 {namespace => (
3 <NavigationLink
4 destination={
5 <DetailPage
6 navigationTransition={{
7 type: "zoom",
8 namespace,
9 sourceID: "card-1"
10 }}
11 />
12 }
13 >
14 <VStack
15 frame={{
16 width: 280,
17 height: 180
18 }}
19 background="systemGray6"
20 matchedTransitionSource={{
21 id: "card-1",
22 namespace
23 }}
24 >
25 <Text>Card Title</Text>
26 </VStack>
27 </NavigationLink>
28 )}
29</NamespaceReader>
Effect:
- The entire card becomes the transition origin
- The detail page expands naturally from that card
- Produces a classic Hero-style animation
7. Difference Between matchedTransitionSource and matchedGeometryEffect
| Aspect |
matchedTransitionSource |
matchedGeometryEffect |
| Scope |
Page-level navigation |
Component-level layout |
| Requires Navigation |
Yes |
No |
| Multiple elements supported |
No |
Yes |
Needs sourceID |
Yes |
No |
| Geometry property control |
No |
Yes |
| Internal layout animation |
No |
Yes |
Summary:
matchedTransitionSource: controls where a page transition starts
matchedGeometryEffect: controls how layout changes animate inside views
8. Common Issues and Debug Checklist
8.1 Transition Does Not Trigger
Check:
- Does
sourceID exactly match matchedTransitionSource.id?
- Are both using the same
namespace instance?
- Is the navigation actually triggered via
NavigationLink?
8.2 Wrong Direction or Scaling Artifacts
Common causes:
-
The source view uses:
scaleEffect
offset
rotation
-
Or is wrapped by:
clipShape
mask
containerShape
These affect how the system reads the real geometry frame.
8.3 Multiple Sources with the Same ID
Incorrect:
- Multiple views share the same
id
- All define
matchedTransitionSource
Result:
- The system cannot determine the true source
- The transition becomes undefined or fails
9. Platform and Environment Limitations
-
matchedTransitionSource works only with:
- Navigation-based transitions
-
It is not supported or is limited in:
-
It should not be used for:
- Tab switching
- Collapsing/expanding menus
- Component state animations
Use matchedGeometryEffect for those cases.
10. Recommended Use Cases
Highly suitable:
- Image → full-screen preview
- Article cover → reading page
- Product card → product detail
- Avatar → profile page
- Large card → immersive detail view
Not suitable:
- High-frequency UI state changes
- Dense grid transitions
- Real-time updating interfaces