SwiftUI 中的条件 onTapGesture

发布于 2025-01-18 15:14:17 字数 1548 浏览 6 评论 0 原文

当其标签( myView )被取决于编辑模式(或任何其他条件)时,我需要不同的行为

  1. 我有一个导航链接, 编辑模式,我想触发导航链接,并使用选定的模型显示 lidetview
  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)
}

这种方法的问题是,IF-和其他分支中的视图不同(由于 ontapgesture modifier)和Swiftui并不认为它们是相同的观点。因此,动画不能被插值并且无法正常工作。另外, myview 每次切换 editmode 总是会失去其状态。

(这是克里斯·埃德霍夫(Chris Eidhof)的一个很好的解释:

所以我继续前进,并将if-statement移至 ontapgesture modifier中如下所示,因此我没有两个不同的 myview s:

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

问题是,现在需求#1不再起作用: ontapgesture 完全吞下了点击手势因此,从未触发导航链接以显示详细信息View 。有意义。

现在我的问题是:

如果没有这些弊端,我该如何获得所需的行为?

I have a navigation link and I need a different behavior when its label (MyView) is tapped depending on the edit mode (or any other condition):

  1. If we are not in edit mode, I want to trigger the navigation link and show the DetailView with the selected model.
  2. If we are in edit mode, I don't want to trigger the navigation link and show an EditingView in a modal sheet instead.

Here's a way to implement this that I came up with:

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

The problem with this approach is that the views in the if- and the else-branch have not the same type (due to the onTapGesture modifier) and SwiftUI doesn't recognize them as the same view. Thus, animations cannot be interpolated and don't work properly. Also, MyView always loses its state each time editMode is toggled.

(Here's a great explanation from Chris Eidhof on why that happens: https://www.objc.io/blog/2021/08/24/conditional-view-modifiers/)

So I went ahead and moved the if-statement inside the onTapGesture modifier as follows so that I don't have two different 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)
}

Problem with this is that now requirement #1 doesn't work anymore: The onTapGesture completely swallow the tap gesture and thus the navigation link is never trigged to show the DetailView. Makes sense.

Now my question is:

How can I get the desired behavior without any of these downsides?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

南…巷孤猫 2025-01-25 15:14:17

简而言之,您想要将: 更改

if editMode == .active {
    MyView()
        .onTapGesture {
            editingModel = model
        }
} else {
    MyView()
}

为:

MyView()
    .allowsHitTesting(editMode == .active)
    .onTapGesture {
        editingModel = model
    }

这可以解决问题,因为现在 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)")
                }
            }
        }
    }
}
struct MyView: View {
    var body: some View {
        Text("MyView")
            .frame(maxWidth: .infinity, alignment: .leading)
            .contentShape(Rectangle())
    }
}
struct EditingModel: Identifiable {
    var id: String { tag }
    let tag: String
}

将内部标签位更改为:

MyView()
    .allowsHitTesting(editMode == .active)
    .onTapGesture {
        editingModel = EditingModel(tag: "model")
    }

In short, you want to change:

if editMode == .active {
    MyView()
        .onTapGesture {
            editingModel = model
        }
} else {
    MyView()
}

Into:

MyView()
    .allowsHitTesting(editMode == .active)
    .onTapGesture {
        editingModel = model
    }

This fixes the issue because now the onTapGesture is only triggered when it can actually listen to touches. It can only trigger when editMode == .active, because otherwise the hit testing is disabled.


Full example:

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)")
                }
            }
        }
    }
}
struct MyView: View {
    var body: some View {
        Text("MyView")
            .frame(maxWidth: .infinity, alignment: .leading)
            .contentShape(Rectangle())
    }
}
struct EditingModel: Identifiable {
    var id: String { tag }
    let tag: String
}

Changing the inner label bit to:

MyView()
    .allowsHitTesting(editMode == .active)
    .onTapGesture {
        editingModel = EditingModel(tag: "model")
    }
还不是爱你 2025-01-25 15:14:17

在乔治的建议的基础上,我最终得到了以下自定义修饰符:

extension View {
    func onTapGestureIf(_ condition: Bool, closure: @escaping () -> ()) -> some View {
        self.allowsHitTesting(condition)
            .onTapGesture {
                closure()
            }
    }
}

像魅力一样工作,这样,我不必指定相同的条件 edingmode == .Active 两次)。强烈建议。

Building on George's suggestion, I ended up with the following custom modifier:

extension View {
    func onTapGestureIf(_ condition: Bool, closure: @escaping () -> ()) -> some View {
        self.allowsHitTesting(condition)
            .onTapGesture {
                closure()
            }
    }
}

Works like a charm and this way, I don't have to specify the same condition editMode == .active twice (single source of truth). Highly recommended. ????

MyView()
    .onTapGestureIf(editMode == .active) {
        editingCounter = counter
    }
初见终念 2025-01-25 15:14:17

使用Group来包裹onTapGesture。就是这样。

extension View {
  public func onTapGesture(isOn: Bool, count: Int = 1, perform action: @escaping () -> Void) -> some View {
    Group {
      if isOn {
        self.onTapGesture(count: count, perform: action)
      } else {
        self
      }
    }
  }
}

Use Group to wrap onTapGesture. That's it.

extension View {
  public func onTapGesture(isOn: Bool, count: Int = 1, perform action: @escaping () -> Void) -> some View {
    Group {
      if isOn {
        self.onTapGesture(count: count, perform: action)
      } else {
        self
      }
    }
  }
}
兔姬 2025-01-25 15:14:17

您可以使用自定义修饰符。

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: {
                MyView()
                    .modifier(conditionalTapGesture(editMode: $editMode, editingModel: $editingModel))
            }
            .sheet(item: $editingModel) { model in
                Text("EditingView: \(model.tag)")
            }
        }
    }
}

}

struct conditionalTapGesture : ViewModifier {
    @Binding var editMode: EditMode
    @Binding var editingModel: EditingModel?
    @ViewBuilder func body(content: Content) -> some View {
        if editMode == .active {
            content.onTapGesture {
                editingModel = EditingModel(tag: "model")
            }
        }
    }
}

you can use custom modifier.

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: {
                MyView()
                    .modifier(conditionalTapGesture(editMode: $editMode, editingModel: $editingModel))
            }
            .sheet(item: $editingModel) { model in
                Text("EditingView: \(model.tag)")
            }
        }
    }
}

}

struct conditionalTapGesture : ViewModifier {
    @Binding var editMode: EditMode
    @Binding var editingModel: EditingModel?
    @ViewBuilder func body(content: Content) -> some View {
        if editMode == .active {
            content.onTapGesture {
                editingModel = EditingModel(tag: "model")
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文