SwiftUI Picker With Optional Selection

How do you create a SwiftUI picker that works with optional selection so that not picking a value is possible?

SwiftUI Picker

A SwiftUI picker expects a binding to a selection. For example, suppose I have a project model that is Identifiable:

struct Project: Identifiable {
  let id = UUID()
  var name: String
  ...
}

To pick a single project from a store of projects:

struct ProjectPicker: View {
  @Binding var project: Project.ID
  @Environment(ProjectStore.self) private var store
  
  var body: some View {
    Picker(selection: $project) {
      ForEach(store.projects) { project in
        Text(project.name)
      }
    } label: {
      Text("Project")
    }
  }
}

The ForEach automatically applies a tag to each Text view using the id of the project. Selecting an item in the picker sets the pickers binding to the identifier of the item. Here’s how that looks when part of a Form to create an item in a project:

Project picker with three items named one, two, three. Two is selected

Optional Selection

Sometimes I need a picker to work with an optional binding. Either because I don’t yet have a selection or because not selecting an item is also a valid choice:

struct ProjectPicker: View {
  @Binding var project: Project.ID?
  ...
}

Apple provides an example on how to make this work in the documentation for the tag modifier. We need to manually tag each of the picker views with an optional identifier matching the optional type of the picker selection. The tag modifier has a parameter to indicate you want to make it optional:

Text(project.name)
  .tag(project.id, includeOptional: true)

Since the includeOptional parameter defaults to true it’s enough to tag the views:

Text(project.name)
  .tag(project.id)

Finally, we can also include a None option for when no project is selected. Note we need to cast nil as an optional project ID:

Text("None")
  .tag(nil as Project.ID?)

The full project picker with optional selection:

struct ProjectPicker: View {
  @Binding var project: Project.ID?
  @Environment(ProjectStore.self) private var store
  
  var body: some View {
    Picker(selection: $project) {
      Text("None")
        .tag(nil as Project.ID?)
      ForEach(store.projects) { project in
        Text(project.name)
          .tag(project.id)
      }
    } label: {
      Text("Project")
    }
  }
}

Picker with four options, none (selected), one, two, three