分段选择器通过onTapGesture无法响应点击

8

我尝试重新实现分段控制器,因为它们在Xcode 11 beta 5中已过时。花了一些时间,但最终得到了我想要的外观。但是,当我用onTapGesture()替换tapAction时,选择器停止工作。

以下代码显示了问题。注释掉pickerStyle会得到一个轮式选择器,它可以与onTapGesture()一起使用。

import SwiftUI

var oneSelected = false
struct ContentView: View {
    @State var sel = 0
    var body: some View {
        VStack {
            Picker("Test", selection: $sel) {
                Text("A").tag(0)
                Text("B").tag(1)
                Text("C").tag(2)
            }
            .pickerStyle(SegmentedPickerStyle())
            Picker("Test", selection: $sel) {
                Text("A").tag(0)
                Text("B").tag(1)
                Text("C").tag(2)
            }
            .pickerStyle(SegmentedPickerStyle())
            .onTapGesture(perform: {
                oneSelected = (self.sel == 1)
            })
            Text("Selected: \(sel)")
        }
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

我希望Picker().pickerStyle(SegmentedPickerStyle())的工作方式与SegmentedController()相同。

4个回答

6
您添加的tapGesture干扰了选择器内置的点按手势识别,这就是为什么在选择器被轻按时会运行您的.onTapGesture代码,但选择器本身不响应轻按的原因。在您的情况下,我建议采用不同的方法:将符合ObservableObject的视图模型传递到ContentView中,并在其中包含一个用于选择器选项的@Published变量。然后向该变量添加属性观察器,检查所选选项是否为1。
例如:
class ViewModel: ObservableObject {
    @Published var sel = 0 {
        didSet {
            oneSelected = oldValue == 1
        }
    }
    var oneSelected = false
}

在 `SceneDelegate.swift` 文件中,或者无论在哪里声明了 `ContentView`:
ContentView().environmentObject(ViewModel())

ContentView.swift中:

@EnvironmentObject var env: ViewModel
var body: some View {
    VStack {
        Picker("Test", selection: $env.sel) {
            Text("A").tag(0)
            Text("B").tag(1)
            Text("C").tag(2)
        }
        .pickerStyle(SegmentedPickerStyle())
        Picker("Test", selection: $env.sel) {
            Text("A").tag(0)
            Text("B").tag(1)
            Text("C").tag(2)
        }
        .pickerStyle(SegmentedPickerStyle())
        Text("Selected: \(sel)")
    }
}
注意: 根据我的经验,在之前的beta版本中向SegmentedControl添加tapGesture会导致SegmentedControl不响应,所以我不确定为什么在之前的版本中它能够正常工作。截至SwiftUI beta 5,我不认为有一种方法可以将手势分配优先级。 编辑: 您可以使用.highPriorityGesture()使您的手势优先于视图中定义的手势,但是您的手势具有更高的优先级会导致问题。然而,您可以使用.simultaneousGesture(),我认为这可能是您问题的解决方案,但是SwiftUI Beta 5似乎并不完全功能正常。

1
我原本计划做一些类似但更简单的事情。如果我删除pickerStyle(),我的代码中的第二个选择器将起作用,因此这是分段代码中有问题的。 - Michael Salmon
可能是因为分段控件的代码具有不同的手势相关代码,因为视图需要知道用户在视图内点击的位置,而在默认选择器中,不需要知道点击在视图内的位置。由于我们没有SwiftUI的源代码,所以无法确切地知道为什么分段控件选择器样式的点击手势识别优先级低于手动添加的点击手势,但默认选择器并非如此。 - RPatel99
为了进一步说明这是与手势优先级相关的问题,如果您向默认选择器添加一个tapGesture,选择器会正确响应您的触摸,但您手动添加的tapGesture不会被注册。在这种情况下,选择器的手势识别优先于您手动添加的手势。由于无法访问SwiftUI源代码,我无法确定为什么会出现这种情况。 - RPatel99
感谢您的时间。我的解决方案是创建一个包装ObservableObject类,用于Int类型,并在其值更改时调用闭包。现在我不需要担心轻敲了。 - Michael Salmon
@MichaelSalmon 没问题,这就是我建议你采取的方法。如果我的回答对你有帮助并且详尽无遗,你能接受这个答案吗? - RPatel99

2
class IndexManager: ObservableObject {
    @Published var index = 0 {
        didSet {
            publisher.send(index)
        }
    }
    let publisher = PassthroughSubject<Int, Never>()
}

struct SegmentedPickerView: View {

    private let strings = ["a", "b", "c"]
    @ObservedObject private var indexManager = IndexManager()

    var body: some View {
        Picker("", selection: $indexManager.index) {
            ForEach(strings, id: \.self) {
                Text($0).tag(self.strings.firstIndex(of: $0)!)
            }
        }.pickerStyle(SegmentedPickerStyle())
            .onReceive(indexManager.publisher) { int in
                print("onReceive \(int)")
        }
    }
}

对我来说是绝对的答案。 - Rajan Twanabashu

1
我已经使用以下条件在 onTapGesture 中使其正常工作了。
@State private var profileSegmentIndex = 0    

Picker(selection: self.$profileSegmentIndex, label: Text("Music")) {
                    Text("My Posts").tag(0)
                
                Text("Favorites").tag(1)
            }
            .onTapGesture {
                if self.profileSegmentIndex == 0 {
                    self.profileSegmentIndex = 1
                } else {
                    self.profileSegmentIndex = 0
                }
            }

这是最容易实现的解决方案。因为似乎选择器内置的轻拍手势受到了干扰;通过明确定义选择器的工作方式,您基本上正在纠正该干扰。谢谢! - griv

0

.onTapGesture() 在具有 .pickerStyle 属性值为 SegmentedPickerStyle() 的 Picker 上似乎无法正常工作。

一个对我有效的替代方法(来自 this answer)是使用以下方式调用 .onReceive()

                Picker("", selection: $quoteFrequencyIndex) {
                    ForEach(0..<frequencyOptions.count, id: \.self) { index in
                        Text(self.frequencyOptions[index])
                            .foregroundColor(colorPalettes[safe: colorPaletteIndex]?[1] ?? .white)
                    }
                }
                .pickerStyle(SegmentedPickerStyle())
                .onReceive([self.quoteFrequencyIndex].publisher.first()) { _ in
                    WidgetCenter.shared.reloadTimelines(ofKind: "QuoteDropletWidget")
                }

抽象成一个通用模板,您可以在onReceive()调用中放入自己的逻辑,由您的selection变量控制:
Picker(selection: ${variable}){}
.pickerStyle(SegmentedPickerStyle())
.onReceive([self.{variable}].publisher.first()) {}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接