Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Segmented Picker with onTapGesture doesn't respond to taps

I tried to reimplement the SegmentedControlers that I was using as they became deprecated in Xcode 11 beta 5. It took a while but I got the look I wanted. However when I replaced the tapAction with an onTapGesture() then the picker stopped working.

The code below shows the problem. Commenting out the pickerStyle gets a wheel picker which does work with 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

I expect that Picker().pickerStyle(SegmentedPickerStyle()) should work the same way as SegmentedController() did.

like image 560
Michael Salmon Avatar asked Jul 30 '19 12:07

Michael Salmon


2 Answers

The tapGesture you added interferes with the picker's built in tap gesture recognizing, which is why the code in your .onTapGesture runs when the picker is tapped, but the picker itself is not responding to taps. In your case, I suggest a different approach: pass a view model that conforms to ObservableObject into your ContentView, and have it contain an @Published variable for the picker selection. Then add a property observer to that variable that checks if the selected option is 1.

For example:

class ViewModel: ObservableObject {
    @Published var sel = 0 {
        didSet {
            oneSelected = oldValue == 1
        }
    }
    var oneSelected = false
}

In SceneDelegate.swift, or wherever ContentView is declared:

ContentView().environmentObject(ViewModel())

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

Note: In my experience, adding a tapGesture to a SegmentedControl in previous betas resulted in the SegmentedControl being unresponsive, so I'm not sure why it was working for you in previous version. As of SwiftUI beta 5, I don't think there is a way to assign priority levels to gestures.

Edit: You can use .highPriorityGesture() to make your gesture take precedence over gestures defined in the view, but your gesture having higher precedence is causing your problem. You can, however, use .simultaneousGesture(), which I thought would be the solution to your problem, but I don't think it is fully functional as of SwiftUI Beta 5.

like image 123
RPatel99 Avatar answered Nov 12 '22 22:11

RPatel99


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)")
        }
    }
}
like image 41
Steve Ham Avatar answered Nov 12 '22 22:11

Steve Ham