Normally I can display a list of items like this in SwiftUI:
enum Fruit {
case apple
case orange
case banana
}
struct FruitView: View {
@State private var fruit = Fruit.apple
var body: some View {
Picker(selection: $fruit, label: Text("Fruit")) {
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue).tag(fruit)
}
}
}
}
This works perfectly, allowing me to select whichever fruit I want. If I want to switch fruit
to be nullable (aka an optional), though, it causes problems:
struct FruitView: View {
@State private var fruit: Fruit?
var body: some View {
Picker(selection: $fruit, label: Text("Fruit")) {
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue).tag(fruit)
}
}
}
}
The selected fruit name is no longer displayed on the first screen, and no matter what selection item I choose, it doesn't update the fruit value.
How do I use Picker with an optional type?
The tag must match the exact data type as the binding is wrapping. In this case the data type provided to tag
is Fruit
but the data type of $fruit.wrappedValue
is Fruit?
. You can fix this by casting the datatype in the tag
method:
struct FruitView: View {
@State private var fruit: Fruit?
var body: some View {
Picker(selection: $fruit, label: Text("Fruit")) {
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue).tag(fruit as Fruit?)
}
}
}
}
Bonus: If you want custom text for nil
(instead of just blank), and want the user to be allowed to select nil
(Note: it's either all or nothing here), you can include an item for nil
:
struct FruitView: View {
@State private var fruit: Fruit?
var body: some View {
Picker(selection: $fruit, label: Text("Fruit")) {
Text("No fruit").tag(nil as Fruit?)
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue).tag(fruit as Fruit?)
}
}
}
}
Don't forget to cast the nil
value as well.
Why not extending the enum with a default value? If this is not what you are trying to achieve, maybe you can also provide some information, why you want to have it optional
.
enum Fruit: String, CaseIterable, Hashable {
case apple = "apple"
case orange = "orange"
case banana = "banana"
case noValue = ""
}
struct ContentView: View {
@State private var fruit = Fruit.noValue
var body: some View {
VStack{
Picker(selection: $fruit, label: Text("Fruit")) {
ForEach(Fruit.allCases, id:\.self) { fruit in
Text(fruit.rawValue)
}
}
Text("Selected Fruit: \(fruit.rawValue)")
}
}
}