通常我可以在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与可选类型一起使用?
答案 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()
}
}