Matched Geometry Effect
Matched Geometry Effect is a very helpful modifier when animating. Internally, it maintains a list of source geometries. A geometry is the size and position of a view. The matchedGeometryEffect modifier allows us to mark a view as either source or target. When the view is marked as a source view, its geometry is recorded. For matched geometry targets, the subtree receives a proposed size that's exactly the size of the source, and is positioned exactly at the source position. For this, both the id and namespace need to match.
There are two very common use cases of matched geometry effect. First, it can be used to overlay/underline/highlight a specific view and animate when the source changes. Second, it is often used when a view is "moved" to a different part of the view tree. We'll look at both in detail.
Static Sources and Targets
In the example below, we can see a picker with three options. Each button has a matched geometry effect applied. The isSource parameter is true by default, which means all three geometries are recorded, each with their own id. The underline or capsule has isSource set to false, so it gets the geometry of the current selection. When we select a different button, it's only the position and size that are animated:
struct PickerExample: View { @Namespace private var namespace @State private var selection = 0 let titles = ["Inbox", "Sent", "Archive"] var body: some View { HStack { ForEach(0..<3, id: \.self) { index in Text(titles[index]) .padding() .matchedGeometryEffect( id: index, in: namespace ) .onTapGesture { withAnimation { selection = index } } } } .overlay { Rectangle() .frame(height: 2) .frame(maxHeight: .infinity, alignment: .bottom) .matchedGeometryEffect( id: selection, in: namespace, isSource: false ) } .foregroundStyle(.blue) }}
Transitions
Matched Geometry Effect is very useful when a view moves its position in the tree. To SwiftUI, the same view in two different positions is simply two different views. In the example below, SwiftUI considers both branches to be completely different views. Without a matched geometry effect SwiftUI will fade between the two rectangles. With a matched geometry effect in the correct place, it works as expected.
There are two things to watch out for. In the example below, both branches of the if statement have isSource set to true(this is the default). During the transition, the newly inserted view will animate from the old geometry. Likewise, the old view will animate to the new view's geometry. In order for this to work, it's best to apply the matched geometry effect as close to the leaf view as possible. As we can see below, when we put the matched geometry effect after the frame it doesn't work as expected, because the frame overrides the proposal:
struct ContentView: View { @State private var isHero = false @Namespace private var ns var body: some View { let rect = Color.yellow .onTapGesture { isHero.toggle() } ZStack { if isHero { rect .matchedGeometryEffect( id: "rect", in: ns ) } else { rect .matchedGeometryEffect( id: "rect", in: ns ) .frame(width: 30, height: 30) } } .animation(.default, value: isHero) }}
Another thing to keep in mind is that, by default, the opacity transition is used to animate between the two views. When we're unaware of this, we might be surprised because it looks like the view is slightly transparent for a bit during the animation. In the example above, this effect shows up as a slight color change during the animation. In theory, we can override this behavior by explicitly changing the opacity transition to .identity if we want to avoid the fade effect. In practice, something like a .scale(1)transition works better (for reasons we don't fully understand).
References

In this blog post, we look at why modifier order matters and how a fixed frame can override the proposed size from a matched geometry effect.
https://chris.eidhof.nl/post/matched-geometry-effect/
In this three part-series on Swift Talk, we reimplement matched geometry effect. This shows that semantically, we can understand it as "just" applying a frame and position.
https://talk.objc.io/episodes/S01E260-matched-geometry-effect-part-3
In the series around building a photo grid, we show how to use a matched geometry effect to achieve hero animations. Getting it just right takes quite a bit more effort than just adding a matched geometry effect. We need to be able to turn off the matched geometry effect, add a custom transition, get the layout just right and add the correct clipping.
https://talk.objc.io/episodes/S01E310-building-a-photo-grid-animations