Normally I can display a list of items like this in SwiftUI:
enum Fruit {
case apple
case orange
case banana
}
struct FruitView: View {
@State
I actually prefer @Senseful's solution for a point solution, but for posterity: you could also create a wrapper enum, which if you have a ton of entity types in your app scales quite nicely via protocol extensions.
// 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: 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> {
map { .some($0) }
}
var optionalPickable: Array> {
[.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)
}
}
Usage is then quite tight:
Picker(selection: $task.job, label: Text("Job")) {
ForEach(Model.shared.jobs.optionalPickable) { p in
p.itemView
}
}