SwiftUI 中的条件 onTapGesture

Mis*_*cha 5 editmode ios swift swiftui swiftui-navigationlink

MyView我有一个导航链接,当根据编辑模式(或任何其他条件)点击其标签 ( ) 时,我需要不同的行为:

  1. 如果我们处于编辑模式,我想触发导航链接并显示DetailView所选模型。
  2. 如果我们处于编辑模式,我不想触发导航链接并EditingView在模式表中显示 。

这是我想出的一种实现方法:

NavigationLink(tag: model, selection: $displayedItem) {
    DetailView(model: model)
} label: {
    if editMode == .active {
        MyView()
            .onTapGesture {
                editingModel = model
            }
    } else {
        MyView()
    }
}
.sheet(item: $editingModel) { model in
    EditingView(model: model)
}
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是 if- 和 else- 分支中的视图具有不同的类型(由于修饰符onTapGesture),并且 SwiftUI 不会将它们识别为相同的视图。因此,动画无法插值并且无法正常工作。此外,每次切换MyView时总是会丢失其状态。editMode

(Chris Eidhof 对为什么会发生这种情况做了很好的解释:https://www.objc.io/blog/2021/08/24/conditional-view-modifiers/

所以我继续将 if 语句移动到onTapGesture修饰符内,如下所示,这样我就不会有两个不同的MyViews:

NavigationLink(tag: model, selection: $displayedItem) {
    DetailView(model: model)
} label: {
    MyView()
        .onTapGesture {
            if editMode == .active { // moved
                editingModel = model
            }                        // moved
        }
    }
}
.sheet(item: $editingModel) { model in
    EditingView(model: model)
}
Run Code Online (Sandbox Code Playgroud)

问题是,现在要求#1 不再起作用:onTapGesture完全吞没点击手势,因此导航链接永远不会被触发以显示DetailView. 说得通。

现在我的问题是:

我怎样才能获得所需的行为而没有任何这些缺点?

Geo*_*e_E 9

简而言之,你想要改变:

if editMode == .active {
    MyView()
        .onTapGesture {
            editingModel = model
        }
} else {
    MyView()
}
Run Code Online (Sandbox Code Playgroud)

进入:

MyView()
    .allowsHitTesting(editMode == .active)
    .onTapGesture {
        editingModel = model
    }
Run Code Online (Sandbox Code Playgroud)

这解决了这个问题,因为现在onTapGesture只有当它实际上可以监听触摸时才会触发。它只能在 时触发editMode == .active,否则命中测试将被禁用。


完整示例:

struct ContentView: View {
    @State private var displayedItem: String?
    @State private var editingModel: EditingModel?
    @State private var editMode: EditMode = .inactive

    var body: some View {
        NavigationView {
            List {
                Button("Edit mode: \(editMode == .active ? "active" : "inactive")") {
                    if editMode == .active {
                        editMode = .inactive
                    } else {
                        editMode = .active
                    }
                }

                NavigationLink(tag: "model", selection: $displayedItem) {
                    Text("DetailView")
                } label: {
                    if editMode == .active {
                        MyView()
                            .onTapGesture {
                                editingModel = EditingModel(tag: "model")
                            }
                    } else {
                        MyView()
                    }
                }
                .sheet(item: $editingModel) { model in
                    Text("EditingView: \(model.tag)")
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
struct MyView: View {
    var body: some View {
        Text("MyView")
            .frame(maxWidth: .infinity, alignment: .leading)
            .contentShape(Rectangle())
    }
}
Run Code Online (Sandbox Code Playgroud)
struct EditingModel: Identifiable {
    var id: String { tag }
    let tag: String
}
Run Code Online (Sandbox Code Playgroud)

将内部标签位更改为:

MyView()
    .allowsHitTesting(editMode == .active)
    .onTapGesture {
        editingModel = EditingModel(tag: "model")
    }
Run Code Online (Sandbox Code Playgroud)