Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

SwiftUI Picker in reusable component with protocol cannot conform to Hashable

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?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

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)
    }
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading