MatchedGeometryEffect
Tạo shared element transitions giữa các views.
1. Cơ bản
struct ContentView: View {
@Namespace private var animation
@State private var isExpanded = false
var body: some View {
if isExpanded {
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.matchedGeometryEffect(id: "shape", in: animation)
.frame(width: 300, height: 200)
} else {
RoundedRectangle(cornerRadius: 10)
.fill(.blue)
.matchedGeometryEffect(id: "shape", in: animation)
.frame(width: 50, height: 50)
}
}
}2. Hero Animation
struct PhotoGrid: View {
@Namespace private var namespace
@State private var selectedPhoto: Photo?
var body: some View {
ZStack {
// Grid
LazyVGrid(columns: columns) {
ForEach(photos) { photo in
Image(photo.name)
.matchedGeometryEffect(id: photo.id, in: namespace)
.onTapGesture {
withAnimation(.spring()) {
selectedPhoto = photo
}
}
}
}
// Detail overlay
if let photo = selectedPhoto {
Image(photo.name)
.matchedGeometryEffect(id: photo.id, in: namespace)
.onTapGesture {
withAnimation(.spring()) {
selectedPhoto = nil
}
}
}
}
}
}3. Tab Indicator
struct AnimatedTabs: View {
@Namespace private var namespace
@State private var selectedTab = 0
let tabs = ["Home", "Search", "Profile"]
var body: some View {
HStack {
ForEach(tabs.indices, id: \.self) { index in
VStack {
Text(tabs[index])
if selectedTab == index {
Rectangle()
.fill(.blue)
.frame(height: 2)
.matchedGeometryEffect(id: "tab", in: namespace)
} else {
Rectangle()
.fill(.clear)
.frame(height: 2)
}
}
.onTapGesture {
withAnimation(.spring()) {
selectedTab = index
}
}
}
}
}
}📝 Key Points
- Dùng
@Namespaceđể tạo animation namespace - ID phải unique trong namespace
- Cần
withAnimationđể trigger - Great cho hero animations, tab indicators
Last updated on