如何使用 matchedGeometryEffect 在 SwiftUI 中转换子视图?

How to transition subviews in SwiftUI using matchedGeometryEffect?

我有许多 SwapItem 结构,每个结构都有一个 child SwapItemChild。然后,使用 SwiftUI 的 ForEach,我想显示每个 SwapItem 的名称,称为项目视图,还包含一个圆圈,其颜色分别为 SwapItemChild,称为child 查看。随后,我想交换两个项目的 children,并让相应的 child 视图更改位置动画。这受到了 this extensive tutorial 的这种效果的其他示例的启发,但并非特别是 children 视图交换。

我尝试使用 matchedGeometryEffect 通过各自 SwapItemChild 的 ID 识别每个 child 视图来做到这一点。但是,这会导致动画跳动,只有顶部 child 视图向下移动,而底部 child 视图会立即跳到顶部。

功能示例代码如下

// MARK: - Model
struct SwapItem: Identifiable {
    let id = UUID()
    let name: String
    var child: SwapItemChild
}

struct SwapItemChild: Identifiable {
    let id = UUID()
    let color: Color
}

class SwapItemStore: ObservableObject {
    @Published private(set) var items = [SwapItem(name: "Task 1", child: SwapItemChild(color: .red)),
                                         SwapItem(name: "Task 2", child: SwapItemChild(color: .orange))]
    
    func swapOuterChildren(){
        let tmpChild = items[0].child
        items[0].child = items[1].child
        items[1].child = tmpChild
    }
}

// MARK: - View
struct SwapTestView: View {
    @StateObject private var swapItemStore = SwapItemStore()
    @Namespace private var SwapViewNS
    
    var body: some View {
        VStack(spacing: 50.0) {
            Button(action: swapItemStore.swapOuterChildren){
                Text("Swap outer children")
                    .font(.title)
            }
            
            VStack(spacing: 150.0) {
                ForEach(swapItemStore.items){ item in
                    SwapTestItemView(item: item, ns: SwapViewNS)
                }
            }
        }
    }
}

struct SwapTestItemView: View {
    let item: SwapItem
    let ns: Namespace.ID
    
    var body: some View {
        HStack {
            Circle()
                .fill(item.child.color)
                .frame(width: 100, height: 100)
                .matchedGeometryEffect(id: item.child.id, in: ns)
                .animation(.spring())
            
            Text(item.name)
        }
    }
}

要使这些 child 视图无缝交换位置,matchedGeometryEffect 的正确实现是什么?

我已经遇到过这种问题,试试这个:

ForEach(swapItemStore.items, id: \.self.child.id)

另一种方式:

struct SwapItem: Identifiable, Hashable {
    let id = UUID()
    let name: String
    var child: SwapItemChild
}

struct SwapItemChild: Identifiable, Hashable {
    let id = UUID()
    let color: Color
}

与 :

ForEach(swapItemStore.items, id: \.self)

参见:https://www.hackingwithswift.com/books/ios-swiftui/why-does-self-work-for-foreach