Skip to Content
iOS✨ AnimationsMatchedGeometryEffect

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