I am trying to present a modal that contains an element using matchedGeometryEffect
. I’ve run into a problem — if I animate the transition, the matchedGeometryEffect
animates as well. I’d like it not to animate the first time, but only in response to the user’s action.
Minimal example:
struct Modal: View {
@Namespace private var modalNamespace
var body: some View {
HStack {
Text("Option 1")
.matchedGeometryEffect(id: "option1", in: modalNamespace)
Text("Option 2")
.matchedGeometryEffect(id: "option2", in: modalNamespace)
}
.frame(maxWidth: .infinity)
.background(
Color.blue
.matchedGeometryEffect(id: "option2", in: modalNamespace, isSource: false)
)
}
}
struct ContentView: View {
@State private var showModal: Bool = false
var body: some View {
ZStack {
Button {
withAnimation {
showModal = true
}
} label: {
Text("Show modal")
}
if showModal {
Color.gray
.onTapGesture {
showModal = false
}
VStack {
Spacer()
Modal()
Spacer()
}
.transition(.move(edge: .bottom))
}
}
}
}
In the real app, the modal contains a segmented control that uses matchedGeometryEffect
. I want to keep the animation in response to the user’s action, but I really want to remove this unwanted animation during view's transition.
Can anyone help me slide this modal in without triggering that animation? :)
One way to detach the animation for .matchedGeometryEffect
from the transition is to use a state variable (a flag) to control the visibility of the background:
true
in .onAppear
false
in .onDisappear
.By itself, this does not fix the problem, because the blue background does not animate with the rest of the view when first shown. This can be resolved by applying .geometryGroup()
to the body of the Modal
view:
struct Modal: View {
@Namespace private var modalNamespace
@State private var isShowing = false // 👈 added
var body: some View {
HStack {
Text("Option 1")
.matchedGeometryEffect(id: "option1", in: modalNamespace)
Text("Option 2")
.matchedGeometryEffect(id: "option2", in: modalNamespace)
}
.frame(maxWidth: .infinity)
.background { // 👈 changed
if isShowing {
Color.blue
.matchedGeometryEffect(id: "option2", in: modalNamespace, isSource: false)
}
}
.geometryGroup() // 👈 added
.onAppear { isShowing = true } // 👈 added
.onDisappear { isShowing = false } // 👈 added
}
}