Hello! Welcome back to our SwiftUI animation series.
In the last lesson, we focused on how views enter and exit the screen using the .transition() modifier. This is perfect for when a view either exists or it doesn't. But what about when a single logical element needs to move and transform across the screen, changing its size and position as it goes?
This is where matchedGeometryEffect comes in. Today, you will learn how to implement seamless animations between view states using the matchedGeometryEffect modifier. This powerful tool allows you to create what are often called "Hero" or "Magic Move" animations, where SwiftUI understands that two seemingly different views in your UI are actually the same entity, and it animates the geometric change between them.
By the end of this lesson, you'll be able to build fluid transitions like a thumbnail image expanding into a full detail view, or a selection indicator sliding smoothly between tabs.
1. The Core Concept: Hero Animations
The fundamental idea behind matchedGeometryEffect is to link two or more views that exist at different times or in different parts of your view hierarchy. You are essentially telling SwiftUI, "This small circle and this large rectangle are the same thing. When one disappears and the other appears, please animate the transformation."
To achieve this, you need three key ingredients:
- A
@Namespace: A property wrapper that creates a shared animation context for the views you want to link. - A shared ID: A hashable identifier that you assign to each view within the
matchedGeometryEffectmodifier. This is how SwiftUI knows which views to match. - A state change wrapped in
withAnimation: This is the trigger that causes one view to be removed from the hierarchy and the other to be inserted, initiating the animation.
Let's watch a video that breaks this process down into clear, actionable steps by building a profile view that expands and collapses.
SwiftUI Profile Animation | Matched Geometry Effect
This video from Sean Allen provides an excellent walkthrough of the fundamental 'Hero Animation' pattern. It covers the setup, the trigger, and the application of the effect, including some crucial gotchas.
Watch from the beginning until 07:41. Pay close attention to these three phases: Setup (01:05 - 03:36): How two distinct views (CollapsedProfileView and ExpandedProfileView) are switched using an @State boolean and an if-else block. Applying the Effect (03:36 - 06:00): How @Namespace is created and how .matchedGeometryEffect is applied to the image in both views with a shared ID. The Gotchas (06:00 - 07:41): Notice why the animation initially fails and how withAnimation fixes it. Also, observe the critical placement of the modifier before the .frame() modifier when the view's size is changing.
After watching the video, the core pattern should be clear. Here’s a summary of the steps to create a hero animation:
- Define two views: Create the "source" and "destination" views (e.g., a small thumbnail and a large image).
- Control visibility with state: Use an
@Stateboolean and anif-elseblock to ensure only one of the two views is on screen at a time. - Create a namespace: Add
@Namespace private var animationto your view. - Apply the effect: Add the
.matchedGeometryEffect(id:in:)modifier to the corresponding elements in both views. Use the sameidandnamespacefor both. - Animate the state change: Wrap the state toggle that switches between the views in a
withAnimationblock.
For a more detailed textual explanation of the modifier's parameters, the SwiftUI Lab has a great article.
MatchedGeometryEffect - Part 1 (Hero Animations)
This article, 'MatchedGeometryEffect - Part 1', provides a thorough explanation of the id and namespace parameters.
Read the sections 'The Building Blocks of a Hero Animation', the explanation of the function signature's parameters (id, namespace, properties), and the first code example that follows. This will solidify your understanding of 'the why' behind the code you just saw.
2. Use Case 2: Animating a Selection Indicator
Hero animations are about transitioning between two mutually exclusive views. But matchedGeometryEffect is also perfect for another common scenario: animating a selection indicator that moves between multiple items that are all present in the view hierarchy. Think of a custom segmented control or tab bar.
In this pattern, the items themselves don't animate. Instead, a separate background or underline view is matched to the currently selected item.

Let's watch a practical tutorial on building this exact component.
How to use MatchedGeometryEffect in SwiftUI | Advanced Learning #4
The Swiftful Thinking channel has a great video that builds a custom filter bar. We will focus on the part where he builds this component, as it perfectly demonstrates this second use case.
Watch from 08:51 to 16:09. Observe the structure: An HStack iterates over a list of categories to create the text labels. A RoundedRectangle is placed inside the loop, conditionally shown if its category is the selected one. The .matchedGeometryEffect is applied to this RoundedRectangle, using a consistent ID. When the selection changes, SwiftUI animates the rectangle from its old position and size to its new ones.
This approach is incredibly powerful. You aren't calculating frames or offsets manually. You simply declare which item the indicator should be matched to, and matchedGeometryEffect handles the complex animation of both size and position for you.
There is also another way to structure this, by explicitly marking one view as the "follower". The article "SwiftUI matched geometry effect in a custom segmented control" explains this well. It defines a background capsule and marks it with isSource: false. This tells the capsule to match its geometry to whichever button is currently the source (which defaults to isSource: true). This is an alternative pattern you may encounter.
3. Key Considerations and Gotchas
As you've seen, matchedGeometryEffect is powerful but has a few rules you need to follow for it to work correctly.
- Modifier Order is Crucial: When a view's size is changing, the
.matchedGeometryEffectmodifier must be placed before frame-altering modifiers like.frame(),.padding(), or.offset(). If you place it after, the animation will be calculated based on the final frame, which can lead to unexpected clipping or positioning errors during the transition. - Animate with
withAnimation: The magic only happens if the state change that triggers the appearance/disappearance of the matched views is wrapped in awithAnimationblock. Without it, the change will be instantaneous. - Unique and Consistent IDs: The
idyou provide must be the same for the views you want to link. The@Namespaceprovides the "world" in which these IDs are matched. - What is Being Animated:
matchedGeometryEffectprimarily animates theframeof the view (its position and size). Other properties likecolor,cornerRadius, oropacityare animated because they are animatable properties of the views themselves, and the change is happening within awithAnimationblock. The geometry matching and the property animation happen concurrently, creating a seamless effect.
Conclusion
You've now learned how to create sophisticated, seamless animations that guide the user's eye and create a sense of physical connection between UI elements. This is a significant step towards building polished, professional-grade applications.
Key Takeaways:
matchedGeometryEffectlinks two or more views via a shared@Namespaceand a commonid, telling SwiftUI to animate the geometric properties between them.- The Hero Animation pattern is used for transitions where one view is replaced by another (e.g., a thumbnail expanding to a detail view). This is typically implemented with an
if-elseblock. - The Selection Indicator pattern is used to animate a background or underline view to match the frame of a selected item from a group of visible items.
- Always wrap the state change that triggers the animation in a
withAnimationblock. - Pay close attention to modifier order:
.matchedGeometryEffectshould come before modifiers that alter a view's frame, like.frame()or.padding().
Preview of the Next Lesson:
So far, we've animated views and their properties. But what if the data itself could be animated? In our next lesson, we will dive into a more advanced concept by making a custom type conform to the Animatable protocol. This will allow you to teach SwiftUI how to animate your custom data structures directly, enabling powerful data-driven visualisations like animated chart values or custom progress indicators.