MatchedGeometryEffect
Animate view identity changes across layouts using .matchedGeometryEffect.
Syntax
Matched IDs: Apply .matchedGeometryEffect(id: "id", in: namespace) to two views; SwiftUI animates layout/size/position between them.
Dot Expands to Large Circle
Two circles share the same matched geometry ID.
Toggling state animates size and position between the small and large versions.
Example
Demo.swift
ContentView.swift
App.swift
import SwiftUI
struct MatchedDemo: View {
@Namespace private var ns
@State private var showDetail = false
var body: some View {
VStack(spacing: 16) {
if showDetail {
Circle().matchedGeometryEffect(id: "dot", in: ns).frame(width: 120, height: 120)
} else {
Circle().matchedGeometryEffect(id: "dot", in: ns).frame(width: 40, height: 40)
}
Button("Toggle") { withAnimation(.spring()) { showDetail.toggle() } }
}
.padding()
}
}
In the example above, the two circles share the same matched geometry ID.
Toggling state animates size and position between the small and large versions.
Card Expands to Detail (Shared ID)
A small card animates into a larger detail view using the same matched geometry ID.
The transition preserves visual continuity.
Example
Demo.swift
ContentView.swift
App.swift
import SwiftUI
struct CardsMatchedDemo: View {
@Namespace private var ns
@State private var expanded = false
var body: some View {
VStack(spacing: 16) {
if expanded {
RoundedRectangle(cornerRadius: 16)
.fill(.blue.opacity(0.2))
.matchedGeometryEffect(id: "card", in: ns)
.frame(height: 160)
.overlay(Text("Detail").font(.headline))
} else {
RoundedRectangle(cornerRadius: 12)
.fill(.blue.opacity(0.2))
.matchedGeometryEffect(id: "card", in: ns)
.frame(height: 60)
.overlay(Text("Card").font(.subheadline))
}
Button(expanded ? "Close" : "Open") {
withAnimation(.spring()) { expanded.toggle() }
}
}
.padding()
}
}
In the example above, the small card animates into a larger detail view using the same matched geometry ID.
Leave a Reply