SwiftUI中可选数据类型的选择器?

SwiftUI中可选数据类型的选择器?,swiftui,Swiftui,通常,我可以在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")) {

通常,我可以在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)
            }
        }
    }
}
这非常有效,允许我选择我想要的任何水果。但是,如果我想将
fruit
切换为可为空(也称为可选),则会导致以下问题:

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)
            }
        }
    }
}
选定的水果名称不再显示在第一个屏幕上,并且无论我选择什么选择项,它都不会更新水果值


如何将Picker与可选类型一起使用?

绑定包装时,标记必须与确切的数据类型匹配。在这种情况下,
tag
提供的数据类型是
Fruit
,但
$Fruit.wrappedValue
的数据类型是
Fruit?
。您可以通过在
标记
方法中强制转换数据类型来解决此问题:

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?)
            }
        }
    }
}
奖金:如果您希望为
nil
自定义文本(而不是仅为空),并且希望允许用户选择
nil
(注意:这里要么全部,要么什么都没有),您可以为
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?)
            }
        }
    }
}

不要忘记强制转换
nil
值。

为什么不使用默认值扩展枚举?如果这不是您想要实现的目标,也许您还可以提供一些信息,说明您为什么希望它是可选的

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)")
        }
    }
}

我在这里用Senssuve的解决方案进行了公开回购:

编辑:感谢您对发布链接的评论。下面是回答问题的代码。复制/粘贴就可以了,或者从链接克隆回购协议

import SwiftUI

struct ContentView: View {
    @State private var selectionOne: String? = nil
    @State private var selectionTwo: String? = nil
    
    let items = ["Item A", "Item B", "Item C"]
    
    var body: some View {
        NavigationView {
            Form {
                // MARK: - Option 1: NIL by SELECTION
                Picker(selection: $selectionOne, label: Text("Picker with option to select nil item [none]")) {
                    Text("[none]").tag(nil as String?)
                        .foregroundColor(.red)

                    ForEach(items, id: \.self) { item in
                        Text(item).tag(item as String?)
                        // Tags must be cast to same type as Picker selection
                    }
                }
                
                // MARK: - Option 2: NIL by BUTTON ACTION
                Picker(selection: $selectionTwo, label: Text("Picker with Button that removes selection")) {
                    ForEach(items, id: \.self) { item in
                        Text(item).tag(item as String?)
                        // Tags must be cast to same type as Picker selection
                    }
                }
                
                if selectionTwo != nil { // "Remove item" button only appears if selection is not nil
                    Button("Remove item") {
                        self.selectionTwo = nil
                    }
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

实际上,我更喜欢@sensufe的解决方案作为点解决方案,但对于后代来说:您还可以创建一个包装器枚举,如果您的应用程序中有大量的实体类型,那么它可以通过协议扩展非常好地扩展

//用于确保可以生成默认id的实用程序约束
协议可初始化{
init()
}
//PickerValue包装器上的主约束
协议可选取{
associatedtype元素:可识别,其中Element.ID:EmptyInitializable
}
//隐藏可选性的包装器
枚举PickerValue:Pickable其中元素:可识别,元素ID:EmptyInitializable{
无案例
案例(要素)
}
//包装器上的hashable&eqtable
扩展PickerValue:Hashable和Equatable{
func散列(放入散列程序:inout散列程序){
hasher.combine(id)
}
静态函数==(左:自,右:自)->Bool{
lhs.id==rhs.id
}
}
//常见可识别类型
扩展字符串:EmptyInitializable{}
扩展名Int:EmptyInitializable{}
扩展UInt:EmptyInitializable{}
扩展UInt8:EmptyInitializable{}
扩展UInt16:EmptyInitializable{}
扩展UInt32:EmptyInitializable{}
扩展UInt64:EmptyInitializable{}
扩展UUID:EmptyInitializable{}
//包装器上的id生成器
扩展选择器值:可识别{
变量id:Element.id{
切换自身{
案例。一些(让e):
返回e.id
案例:无:
返回元素。ID()
}
}
}
//要包装为PickerValue的数组上的实用程序扩展
扩展数组,其中Element:可识别,Element.ID:EmptyInitializable{
变量可选取:数组{
地图{.some($0)}
}
var optionalPickable:数组{
[.none]+可拾取
}
}
//使用协议包装的好处是项目视图可以是通用的
//跨数据集。(此处TitleComponent{var title:String{get})
扩展选择器值,其中元素:TitleComponent{
@视图生成器
var itemView:一些视图{
团体{
切换自身{
案例。一些(让e):
文本(如标题)
案例:无:
文本(“无”)
.italic()
.foregroundColor(.accentColor)
}
}
.tag(self)
}
}
使用量相当紧张:

Picker(选择:$task.job,标签:Text(“job”)){
ForEach(Model.shared.jobs.optionalpikable){p in
p、 项目视图
}
}

你能分享你的
水果吗/struct@SimonBachmann:这是一个枚举,更新了问题您的评论对于
枚举
来说足够公平,但是如果它是从数组或集合中选择的,而“以上任何一项”都不是有效的选择,该怎么办@SENSSEVE的回答非常有用!请不要只是发布答案的链接。发布相关代码和链接。这样,您的答案不会消失,它本可以提供的价值也会丢失。虽然此链接可以回答问题,但最好在此处包含答案的基本部分,并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能无效。-我很感激,先生们(@Andrew,@kenny_k)。编辑!此语法的一些变体:
.tag(nil作为水果?
.tag(水果?.none)
.tag(水果?(nil))
.tag(可选.none)