4

I use a form with a picker, and everything works fine (I am able to select an element from the picker), but I cannot deselect it. Does there exist a way to deselect an item from the picker? Thank you!

enter image description here

Picker(selection: $model.countries, label: Text("country")) {
                        ForEach(model.countries, id: \.self) { country in
                            Text(country!.name)
                                .tag(country)
                        }
                    }
Johnas
  • 296
  • 2
  • 5
  • 15

4 Answers4

8

To deselect we need optional storage for picker value, so here is a demo of possible approach.

Tested with Xcode 12.1 / iOS 14.1

demo

struct ContentView: View {
    @State private var value: Int?
    var body: some View {
        NavigationView {
            Form {
                let selected = Binding(
                    get: { self.value },
                    set: { self.value = $0 == self.value ? nil : $0 }
                )
                Picker("Select", selection: selected) {
                    ForEach(0...9, id: \.self) {
                        Text("\($0)").tag(Optional($0))
                    }
                }
            }
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • This solution works like a charm if you don"t want a "Clear" button on your interface – Eric Mar 20 '22 at 22:12
2

I learned almost all I know about SwiftUI Bindings (with Core Data) by reading this blog by Jim Dovey. The remainder is a combination of some research and many hours of making mistakes.

So when I combine Jim's technique to create Extensions on SwiftUI Binding with Asperi's answer, then we end up with something like this...

public extension Binding where Value: Equatable {
    init(_ source: Binding<Value>, deselectTo value: Value) {
        self.init(get: { source.wrappedValue },
                  set: { source.wrappedValue = $0 == source.wrappedValue ? value : $0 }
        )
    }
}

Which can then be used throughout your code like this...

Picker("country", selection: Binding($selection, deselectTo: nil)) { ... }

OR

Picker("country", selection: Binding($selection, deselectTo: someOtherValue)) { ... }
andrewbuilder
  • 3,629
  • 2
  • 24
  • 46
  • Although i think its a brilliant answer and very nice approach, it is not working for a `List()` in SwiftUI. The `set()` is never called, when a row is already selected. – flymg Jan 15 '22 at 21:03
  • 1
    @flymg in my projects this solution works in every scenario with minor changes as needed. The OP asked about deselecting from a picker, but from what you’ve written, your case sounds different. Consider asking a new question and let me know. – andrewbuilder Jan 15 '22 at 22:36
  • Yes you are absolutely right. Just done so here: https://stackoverflow.com/q/70728157/5392813 Appreciate any ideas on this – flymg Jan 16 '22 at 07:35
1

First of, we can fix the selection. It should match the type of the tag. The tag is given Country, so to have a selection where nothing might be selected, we should use Country? as the selection type.

It should looks like this:

struct ContentView: View {
    
    @ObservedObject private var model = Model()
    @State private var selection: Country?
    
    var body: some View {
        NavigationView {
            Form {
                Picker(selection: $selection, label: Text("country")) {
                    ForEach(model.countries, id: \.self) { country in
                        Text(country!.name)
                            .tag(country)
                    }
                }
                
                Button("Clear") {
                    selection = nil
                }
            }
        }
    }
}

You then just need to set the selection to nil, which is done in the button. You could set selection to nil by any action you want.

George
  • 25,988
  • 10
  • 79
  • 133
0

If your deployment target is set to iOS 14 or higher -- Apple has provided a built-in onChange extension to View where you can deselect your row using tag, which can be used like this instead (Thanks)

Picker(selection: $favoriteColor, label: Text("Color")) {
    // ..
}
.onChange(of: favoriteColor) { print("Color tag: \($0)") }
saqib kafeel
  • 296
  • 3
  • 8
  • Update: tried and it doesn't work. If the user selects the same value, the "favoriteColor" doesn't change so the .onChange will not be triggered – Johnas Jan 27 '21 at 20:40