Picker driven by enum

How do you create a picker where the user's selection corresponds to different values of an enumerated type?

Post not yet marked as solved Up vote post of Curiosity Down vote post of Curiosity
8.7k views

Replies

There are two parts. First, you want to make your enum conform to CaseIterable so you can iterate its cases in a ForEach view. Then, when returning the view for each picker option, use the .tag(_:) view modifier to attach the enum value itself to the picker row. SwiftUI will use this to map from views to enum case values.


For localization, it's useful to have a property on your enum that will return a LocalizedStringKey. APIs like Text.init() that take a StringProtocol type actually won't do localization lookups. Those happen only for LocalizedStringKey values, which are ExpressibleByStringInterpolation, meaning that Text("Hello \(name)!") will become localized. If you pass a String variable though, it'll go through the StringProtocol initializer. Thus it's useful to explicitly create one, and to get the long name of LocalizedStringKey out of the way of your view code by hiding it in a property on the type you're describing.


Here's a quick example:


enum Sample: String, Equatable, CaseIterable {
    case first  = "First"
    case second = "Second"
    case third  = "Third"

    var localizedName: LocalizedStringKey { LocalizedStringKey(rawValue) }
}

struct ContentView: View {
    @State var selection: Sample = .first
    var body: some View {
        Form {
            Picker("This Title Is Localized", selection: $selection) {
                ForEach(Sample.allCases, id: \.id) { value in
                    Text(value.localizedName)
                        .tag(value)
                }
            }
        }
        .padding()
    }
}


Note that things get more sticky if your enum has payloads, i.e. case something(String, Int). I'm not sure if those even can be CaseIterable, but beyond that there's the question of how to present a nested sequence of inputs. The answer is going to be highly dependant on your needs, though. Suffice to say that it is possible, but that you may need to create some reasonably complex behaviors to get everything working just right. Combine is your friend here, along with Binding.init(get:set:).

Nice, thanks! FYI, In xcode beta I had to do the following to make this run:

  1. change .id to .self
  2. Nest the form in a NavigationView