When I use matched geometry view modifier I always get a warning
Multiple inserted views in matched geometry group Pair<String, ID>(first: "id1", second: SwiftUI.Namespace.ID(id: 8)) have isSource: true, results are undefined.
While the animation stills works, I'd like to understand why I get this warning and how can I fix the problem.
This is the animation I built, any ideas how to get rid of the warning?
Using the following code
struct ContentView: View {
@State var details = false
@Namespace var animation
var body: some View {
ZStack {
HStack {
Rectangle()
.frame(width: 100, height: 100)
.matchedGeometryEffect(id: "id1", in: animation)
.onTapGesture {
withAnimation {
details.toggle()
}
}
Spacer()
}
.zIndex(1)
if details == true {
AnotherView(details: $details, animation: animation)
.zIndex(2)
}
}
}
}
struct AnotherView: View {
@Binding var details: Bool
var animation: Namespace.ID
var body: some View {
ZStack {
Color.red
Rectangle()
.frame(width: 300, height: 300)
.matchedGeometryEffect(id: "id1", in: animation)
.onTapGesture {
withAnimation {
details.toggle()
}
}
}
}
}
The problem is that you have both Views on screen at the same time (even though the second one is covering the first, the first one is still there). With .matchedGeometryEffect
one view is logically replacing another, so you need to remove the first View when drawing the second View. You can fix this by only drawing the first Rectangle
when !details
.
Also, I moved the .matchedGeometryEffect
to be the first modifier of the Rectangle
s for a cleaner effect.
struct ContentView: View {
@State var details = false
@Namespace var animation
var body: some View {
ZStack {
HStack {
if !details {
Rectangle()
.matchedGeometryEffect(id: "id1", in: animation)
.frame(width: 100, height: 100)
.onTapGesture {
withAnimation {
details.toggle()
}
}
}
Spacer()
}
.zIndex(1)
if details {
AnotherView(details: $details, animation: animation)
.zIndex(2)
}
}
}
}
struct AnotherView: View {
@Binding var details: Bool
var animation: Namespace.ID
var body: some View {
ZStack {
Color.red
Rectangle()
.matchedGeometryEffect(id: "id1", in: animation)
.frame(width: 300, height: 300)
.onTapGesture {
withAnimation {
details.toggle()
}
}
}
}
}
The .matchedGeometryEffect
Documentation states (bolding added):
If inserting a view in the same transaction that another view with the same key is removed, the system will interpolate their frame rectangles in window space to make it appear that there is a single view moving from its old position to its new position. The usual transition mechanisms define how each of the two views is rendered during the transition (e.g. fade in/out, scale, etc), the matchedGeometryEffect() modifier only arranges for the geometry of the views to be linked, not their rendering.
If the number of currently-inserted views in the group with isSource = true is not exactly one results are undefined, due to it not being clear which is the source view.
The following variant works in Preview as well (proposed by @vacawama does not work for me in Preview, just in case).
Tested with Xcode 12.0 / iOS 14
struct ContentView: View {
@State var details = false
@Namespace var animation
var body: some View {
ZStack {
HStack {
if !details {
Rectangle()
.matchedGeometryEffect(id: "id1", in: animation)
.frame(width: 100, height: 100)
.onTapGesture {
details.toggle()
}
}
Spacer()
}.animation(.default, value: details)
if details {
AnotherView(details: $details, animation: animation)
}
}.animation(.default, value: details)
}
}
struct AnotherView: View {
@Binding var details: Bool
var animation: Namespace.ID
var body: some View {
ZStack {
Color.red
Rectangle()
.matchedGeometryEffect(id: "id1", in: animation)
.frame(width: 300, height: 300)
.onTapGesture {
details.toggle()
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With