Prasad De Zoysa
Prasad De Zoysa

Reputation: 2567

SwiftUI Form Picker options are not selecting

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.

Upvotes: 0

Views: 506

Answers (1)

lorem ipsum
lorem ipsum

Reputation: 29614

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.

Upvotes: 1

Related Questions