History
Loading...
Loading...
September 9, 2025
matchedGeometryEffect with a @Namespace to create smooth transitions between views that represent the same logical element. Remember to apply the same id and namespace to both source and destination views for the animation to work properly.This pattern uses a single state variable to control the transition between two views with matchedGeometryEffect, ensuring the animation system can properly interpolate between the different states while maintaining visual continuity.
struct ContentView: View {
@Namespace private var heroAnimation
@State private var isExpanded = false
var body: some View {
VStack {
if !isExpanded {
RoundedRectangle(cornerRadius: 10)
.fill(.blue)
.frame(width: 100, height: 100)
.matchedGeometryEffect(id: "hero", in: heroAnimation)
.onTapGesture {
withAnimation(.spring(response: 0.6)) {
isExpanded = true
}
}
} else {
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.frame(width: 300, height: 200)
.matchedGeometryEffect(id: "hero", in: heroAnimation)
.onTapGesture {
withAnimation(.spring(response: 0.6)) {
isExpanded = false
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}By using conditional rendering with a single boolean state and consistent geometry matching IDs, we avoid common pitfalls like overlapping views or broken animations. The spring animation complements the geometry effect by providing natural motion physics that feels responsive to user interaction.