Learn Courses My Dashboard

Generic struct 'ForEach' requires that 'Data' conform to 'Hashable'

Hi
It seems I still have to learn a lot from CWC even though I am into Module 5. This time XCode complains about:

Generic struct 'ForEach' requires that 'Data' conform to 'Hashable'

This happens in a Picker I am trying to create:

struct DataPicker: View {
    let label: String
    let data: [Data]
    
    @Binding var currentSelection: String
    
    var body: some View {
        HStack(alignment: .bottom) {
            Text("\(label):")
            
            Spacer()

            Picker("", selection: $currentSelection) {

// The following code works and does not give the error                
//                ForEach(0..<data.count, id: \.self) { idx in
//                    Text(data[idx].name).tag(data[idx].name)
//                }
                
                ForEach(data, id: \.self) { item in
                    Text(item.name).tag(item.name)
                }
            }
            .foregroundColor(.black)
        }
    }
}

The commented-out picker code works and does not generate the error. The Data class, which reads in from json, is as follows:

class Data: Identifiable, Decodable {
    var id:UUID?
    var name:String
    var mass:Double
    var fuelConsumption:Double
    var fuelTankCapacity:Double
    var lowFuelThreshold:Double
    
    init() {
        id = UUID()
        name = "My Unnamed Item"
        mass = 30
        fuelConsumption = 6
        fuelTankCapacity = 10
        lowFuelThreshold = 2
    }
}

What am I not understanding here?

You don’t need to use id: \.self since your Data class conforms to Identifiable. You don’t need an id parameter at all, in fact. Just do

ForEach(data) { item in
    Text(item.name).tag(item.name)
}

In order for an array of something to be used in a ForEach SwiftUI has to have some way to tell one item in the array from another. If the items do not conform to Identifiable, you have to supply an id parameter to tell SwiftUI how to distinguish items. That id has to be of some type that conforms to Hashable. When you use \.self, you are saying “each item in the data I am giving you can be used to identify the item itself” and therefore conforms to Hashable.

Your Data class does not conform to Hashable so you get the error.

The reason why ForEach(0..<data.count, id: \.self) works is because Int conforms to Hashable and 0..<data.count is a Range<Int>.

UUID conforms to Hashable, so using a UUID as an id in a struct or class that conforms to Identifiable (which requires a property called id of any type that is Hashable) satisfies the criteria for the id parameter of ForEach.

Also, you really should not name structs/classes with the same name as a system-provided object. Foundation already has a Data class. If you name your custom class Data, then there is the potential for the system to get confused and/or to introduce bugs that might be hard to track down.

Thanks for that great answer! I totally understand now and I am learning a lot. It was not clear to me before when to use the id and when it’s not required. Thanks again!