🪑
ネイティブなアニメーションのためのmatchedGeometryEffect
matchedGeometryEffectあり/なし
matchedGeometryEffectなし
matchedGeometryEffectあり
matchedGeometryEffectの効果
matchedGeometryEffect
無しだと突然赤のボックスが下に遷移する形になっていますが、matchedGeometryEffect
を使うとViewが実際に移動しているようなアニメーションが付与されます。
これは同じViewグループ内で別Viewであっても同一であることをmatchedGeometryEffectで示しているからです。
同じViewグループであることを示すのには@Namespace
を使用します。
コード
struct Person: Identifiable {
let id = UUID()
let name: String
}
struct PersonCell: View {
let person: Person
var body: some View {
RoundedRectangle(cornerRadius: 17)
.foregroundStyle(.red)
.overlay {
Text(person.name)
}
}
}
struct ContentView: View {
let people: [Person] = [
.init(name: "king"),
.init(name: "squid"),
.init(name: "mesh"),
.init(name: "sunrise"),
.init(name: "month"),
.init(name: "tanter"),
]
@Namespace var namespace
@State var unselectedPersonIDs: [UUID] = []
@State var selectedPersonIDs: [UUID] = []
var unselectedPeople: [Person] {
unselectedPersonIDs.map { id in
people.first { $0.id == id }!
}
}
var selectedPeople: [Person] {
selectedPersonIDs.map { id in
people.first { $0.id == id }!
}
}
var body: some View {
VStack {
ScrollView {
LazyVGrid(columns: .init(repeating: .init(), count: 3)) {
ForEach(unselectedPeople) { person in
PersonCell(person: person)
.frame(width: 100, height: 100)
.matchedGeometryEffect(id: person.id, in: namespace)
.onTapGesture {
withAnimation {
selectedPersonIDs.append(person.id)
unselectedPersonIDs.removeAll { $0 == person.id }
}
}
}
}
}
HStack {
ForEach(selectedPeople) { person in
PersonCell(person: person)
.frame(width: 100, height: 100)
.matchedGeometryEffect(id: person.id, in: namespace)
.onTapGesture {
withAnimation {
unselectedPersonIDs.append(person.id)
selectedPersonIDs.removeAll { $0 == person.id }
}
}
}
}
}
.onAppear {
unselectedPersonIDs = people.map(\.id)
}
}
}
Tips
-
アニメーション中のViewにもタップ判定がある
- 描画だけでなくタップも可能
-
綺麗にならないこともある
- Viewの境目?などが原因?
Discussion