Home > Blockchain >  SwiftUI Picker in reusable component with protocol cannot conform to Hashable
SwiftUI Picker in reusable component with protocol cannot conform to Hashable

Time:11-20

I'm trying to build a reusable component that includes a SwiftUI Picker that can work with different types in several places in my app. I created a Pickable protocol that conforms to Hashable, but when I try to use it, the Picker and the ForEach complain that Type 'any Pickable' cannot conform to 'Hashable'

import SwiftUI

struct PickerRow: View {
    let title: String
    let options: [any Pickable]
    @State var selection: any Pickable
    
    var body: some View {
        HStack {
            Spacer()
            Text(title)
                .font(.subHeading)
            Picker(title, selection: $selection, content: {
                ForEach(options, id: \.self) {
                    Text($0.name)
                }
            }).pickerStyle(.menu)
        }
    }
}

protocol Pickable: Hashable {
    var name: String { get }
}

Is there a way to get something like this to work without specifying a concrete type?

CodePudding user response:

If you think about it, it makes sense.

What would you expect to happen if that code was valid and you used it like this?

struct ContentView: View {
    let options = [PickableA(), PickableB()]
    @State var selection = PickableC()
    var body: some View {
        PickerRow(title: "Choose one", options: options, selection: $selection)
    }
}

That couldn't possibly work right?

What you need is a way to make sure that there is a constraint that forces options and selection to be of the same concrete type (consider Equatable for example, both String and Int are conforming, but you cannot compare them).

One possible solution would be a generic constraint in the declaration of your struct (also note the @Binding instead of @State since we modify external values):

struct PickerRow<Option: Pickable>: View {
    let title: String
    let options: [Option]
    @Binding var selection: Option
    
    var body: some View {
        HStack {
            Spacer()
            Text(title)
                .font(.subheadline)
            Picker(title, selection: $selection) {
                ForEach(options, id: \.self) {
                    Text($0.name)
                }
            }.pickerStyle(.menu)
        }
    }
}

which you could use like this:

struct Person: Pickable {
    let name: String
}

struct ContentView: View {
    let options = [Person(name: "Bob"), Person(name: "Alice")]
    @State var selection = Person(name: "Bob")
    var body: some View {
        PickerRow(title: "Choose one", options: options, selection: $selection)
    }
}
  • Related