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

时间:2019-12-15 21:10:03

标签: swiftui

通常我可以在SwiftUI中显示这样的项目列表:

PUT

这非常好用,可以选择任何想要的水果。但是,如果我想将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

所选水果的名称不再显示在第一个屏幕上,并且无论我选择哪个选择项,它都不会更新水果值。

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

4 个答案:

答案 0 :(得分:1)

在绑定包装时,标签必须与确切的数据类型匹配。在这种情况下,提供给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) } } } } 的数据类型为tag,但是Fruit的数据类型为$fruit.wrappedValue。您可以通过在Fruit?方法中强制转换数据类型来解决此问题:

tag

奖金:如果您要为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?) } } } } 值。

答案 1 :(得分:0)

为什么不使用默认值扩展枚举?如果这不是您要实现的目标,也许您还可以提供一些信息,说明为什么要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)")
        }
    }
}

答案 2 :(得分:0)

对于点解决方案,我实际上更喜欢@Senseful的解决方案,但为了后代:您还可以创建一个包装枚举,如果您的应用程序中有大量实体类型,则可以通过协议扩展很好地进行缩放。

// utility constraint to ensure a default id can be produced
protocol EmptyInitializable {
    init()
}

// primary constraint on PickerValue wrapper
protocol Pickable {
    associatedtype Element: Identifiable where Element.ID: EmptyInitializable
}

// wrapper to hide optionality
enum PickerValue<Element>: Pickable where Element: Identifiable, Element.ID: EmptyInitializable {
    case none
    case some(Element)
}

// hashable & equtable on the wrapper
extension PickerValue: Hashable & Equatable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    static func ==(lhs: Self, rhs: Self) -> Bool {
        lhs.id == rhs.id
    }
}

// common identifiable types
extension String: EmptyInitializable {}
extension Int: EmptyInitializable {}
extension UInt: EmptyInitializable {}
extension UInt8: EmptyInitializable {}
extension UInt16: EmptyInitializable {}
extension UInt32: EmptyInitializable {}
extension UInt64: EmptyInitializable {}
extension UUID: EmptyInitializable {}

// id producer on wrapper
extension PickerValue: Identifiable {
    var id: Element.ID {
        switch self {
            case .some(let e):
                return e.id
            case .none:
                return Element.ID()
        }
    }
}

// utility extensions on Array to wrap into PickerValues
extension Array where Element: Identifiable, Element.ID: EmptyInitializable {
    var pickable: Array<PickerValue<Element>> {
        map { .some($0) }
    }
    
    var optionalPickable: Array<PickerValue<Element>> {
        [.none] + pickable
    }
}

// benefit of wrapping with protocols is that item views can be common
// across data sets.  (Here TitleComponent { var title: String { get }})
extension PickerValue where Element: TitleComponent {
    @ViewBuilder
    var itemView: some View {
        Group {
            switch self {
                case .some(let e):
                    Text(e.title)
                case .none:
                    Text("None")
                        .italic()
                        .foregroundColor(.accentColor)
            }
        }
        .tag(self)
    }
}

使用非常紧凑:

Picker(selection: $task.job, label: Text("Job")) {
    ForEach(Model.shared.jobs.optionalPickable) { p in
        p.itemView
    }
}

答案 3 :(得分:-1)

我在这里使用Senseful解决方案进行了公开回购: https://github.com/andrewthedina/SwiftUIPickerWithOptionalSelection

编辑:感谢您对发布链接的评论。这是回答问题的代码。复制/粘贴将达到目的,或者从链接中克隆存储库。

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