Picker for optional data type in SwiftUI?

2020-07-05 08:34发布

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?

标签: swiftui
2条回答
Rolldiameter
2楼-- · 2020-07-05 09:26

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.

查看更多
我想做一个坏孩纸
3楼-- · 2020-07-05 09:33

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)")
        }
    }
}
查看更多
登录 后发表回答