如何正确使用matchedGeometry?

时间:2020-10-28 21:51:52

标签: swiftui

当我使用匹配的几何视图修改器时,我总是收到警告

Multiple inserted views in matched geometry group Pair<String, ID>(first: "id1", second: SwiftUI.Namespace.ID(id: 8)) have isSource: true, results are undefined.

虽然动画静止图像仍然有效,但我想了解为什么收到此警告以及如何解决该问题。

这是我制作的动画,有什么想法摆脱警告吗?

enter image description here

使用以下代码

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()
                    }
                }
        }
    }
}

2 个答案:

答案 0 :(得分:3)

问题是您同时在屏幕上拥有两个视图(即使第二个视图覆盖了第一个视图,第一个视图仍然存在)。使用.matchedGeometryEffect时,一个视图在逻辑上将替换另一个视图,因此在绘制第二个视图时需要删除第一个视图。您可以通过仅在Rectangle时绘制第一个!details来解决此问题。

此外,我将.matchedGeometryEffect移为Rectangle的第一个修饰符,以产生更清晰的效果。

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()
                    }
                }
        }
    }
}

.matchedGeometryEffect Documentation的状态(添加了bold):

如果在同一事务中插入一个视图,并且删除了另一个具有相同键的视图,则系统将在窗口空间中插入其框架矩形,以使似乎有一个视图从其旧位置移至。通常的过渡机制定义了过渡期间如何渲染两个视图中的每个视图(例如淡入/淡出,缩放等),matchedGeometryEffect()修饰符仅将视图的几何布置为被链接,而不是它们的呈现。

如果isSource = true的组中当前插入的视图数不完全是一个未定义的结果,因为不清楚哪个是源视图。


demo in simulator

答案 1 :(得分:0)

以下变体在Preview中也适用(以防万一,@ vacawama提出的建议不适用于Preview)。

通过Xcode 12.0 / iOS 14测试

demo

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()
                }
        }
    }
}