Why can't I select items from this picker view? The difference here is my selection array consist two different Structs('CustomOptionOne' and 'CustomOptionTwo') that conforms to the same protocol 'PickerOption'. The compiler gives me two errors,
Type 'any PickerOption' cannot conform to 'Hashable'
Static method 'buildExpression' requires that 'Content' conform to 'AccessibilityRotorContent'
How can I achieve this?
Any help would be greatly appreciated.
protocol PickerOption: Hashable, Identifiable {
var name: String { get set }
var description: String { get set }
var id: String { get }
}
struct MyView: View {
//I know it's this line, but my array will consist of any object that conforms to PickerOption protocol
private static var exampleStructs: [any PickerOption] = [
CustomOptionOne(name: "Example 1", description: "Example Name 1", id: "5"),
CustomOptionOne(name: "Example 2", description: "Example Name 2", id: "6"),
CustomOptionTwo(name: "Example 3", description: "Example Name 3", id: "7"),
CustomOptionTwo(name: "Example 4", description: "Example Name 4", id: "8")
]
private struct CustomOptionOne: Hashable, Identifiable, PickerOption {
var name: String
var description: String
var id: String
}
private struct CustomOptionTwo: Hashable, Identifiable, PickerOption {
var name: String
var description: String
var id: String
}
@State private var selectedStruct: any PickerOption = CustomOptionOne(name: "Example Name", description: "Example Description", id: "9")
var body: some View {
NavigationView {
Form {
Section(header: Text("Selected Struct")) {
Text(selectedStruct.name)
Text(selectedStruct.description)
}
Section(header: Text("Picker")) {
Picker(selection: $selectedStruct, label: Text(selectedStruct.name), content: {
ForEach(MyView.exampleStructs, id: \.id) { exampleStruct in
Text(exampleStruct.name).tag(exampleStruct)
}
})
}
}
}
}
}
I've checked many answers including this swift picker not selecting item and none of them are answering my use case scenario.
For a Picker
the tag
and the selection
must be of the exact same type your specific example adds an additional layer of complexity because you are trying to use existentials (any
) with SwiftUI.
So the simplest solution is to use the ID
for both the selection
and the tag
but first you have to restrict the ID
which comes from Identifiable
to a specific type so you can match that for the Picker
protocol PickerOption: Hashable, Identifiable where ID == String {
var name: String { get set }
var description: String { get set }
var id: String { get }
}
Then change the property to use a String
@State private var selectedStructId: String? = nil
Then change the tag
to match the selectedStructId
Text(exampleStruct.name).tag(exampleStruct.id as String?)
The whole thing put together will look something like
struct ContentView: View {
//I know it's this line, but my array will consist of any object that conforms to PickerOption protocol
private static var exampleStructs: [any PickerOption] = [
CustomOptionOne(name: "Example 1", description: "Example Name 1", id: "5"),
CustomOptionOne(name: "Example 2", description: "Example Name 2", id: "6"),
CustomOptionTwo(name: "Example 3", description: "Example Name 3", id: "7"),
CustomOptionTwo(name: "Example 4", description: "Example Name 4", id: "8")
]
private struct CustomOptionOne: PickerOption {
var name: String
var description: String
var id: String
}
private struct CustomOptionTwo: PickerOption {
var name: String
var description: String
var id: String
}
@State private var selectedStructId: String? = nil
var body: some View {
NavigationStack {
Form {
if let selectedStructId, let selectedStruct = ContentView.exampleStructs.filter({ o in
o.id == selectedStructId
}).first{ //Find the selected selected struct
Section(header: Text("Selected Struct")) {
Text(selectedStruct.name)
Text(selectedStruct.description)
}
}
Section(header: Text("Picker")) {
Picker(selection: $selectedStructId, label: Text(""), content: {
Text("Select an object.")
ForEach(ContentView.exampleStructs, id: \.id) { exampleStruct in
Text(exampleStruct.name).tag(exampleStruct.id as String?)
}
})
}
}
}
}
}
I know this isn't ideal but existentials are an uphill battle with SwiftUI.