SwiftUI Picker with option for no selection?

Is it possible to create a Picker that gives the user to not make a choice? Here is my existing code:
Code Block swift
 @FetchRequest(entity: ItemCategory.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ItemCategory.name, ascending: true)])
var categories: FetchedResults<ItemCategory>
@State private var selectedCategoryIndex = 0
/* omitted */
Section(header: Text("Category")) {
Picker(selection: $selectedCategoryIndex, label: Text("Category")) {
ForEach(0 ..< categories.count) {
if (categories[$0].name != nil) {
Text(categories[$0].name!)
}
}
}
}

This code allows the user to pick an option, but because I set the selection index to 0 it forces that choice without further interaction. When I use an optional for the index, it breaks selection completely. No item can be selected in that case.

Additionally, 'categories' is a FetchedResults collection.


Post not yet marked as solved Up vote post of blundin Down vote post of blundin
5.3k views

Replies

  1. Make your State variable optional

Code Block
@State private var selectedCategoryIndex = Int?


2. Provide an option for "No selection" and tag it with nil as an optional Int to match it. Place it before or after your ForEach
Code Block
Text("Nothing").tag(nil as Int?)
// ForEach here


3. Cast your actual values as Optionals the same way
Code Block
// ForEach here
Text(categories[$0].name!).tag($0 as Int?)

I'd also consider using the actual object array in your Picker rather than the indices.

I learned almost all I know about SwiftUI Bindings (with Core Data) by reading a blog by Jim Dovey on Core Data Bindings (do a Google search - its worth it). The remainder is a combination of some research and quite a few hours of making mistakes.

So when I use Jim's technique to create Extensions on SwiftUI Binding 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)) { ... }

OR when using .pickerStyle(.segmented)

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

which sets the index of the segmented style picker to -1 as per the documentation for UISegmentedControl and selectedSegmentIndex.

The default value is noSegment (no segment selected) until the user touches a segment. Set this property to -1 to turn off the current selection.