IOS Foundation the id of ForEach (Module 2 Challenge 14)

Hi,

Anyone can explain why “id: . self” isn’t supposed to be used in the 1st ForEach loop?

The code is fine after I remove the 1st id.

It’s needed when your model doesn’t confirm to the protocol Identifiable

Identifiable, means that your model has an id property, and id must be a unique value for every instance

1 Like

But why the 2nd one needs it? They are in the same Models folder that conforms to the “identifiable” protocol.

I tried “id: .image” in the first one, and it worked. For some reason, if you were to use id in the first one. It should be “id: .image” instead of “id: .self”.

When you use

ForEach(model.pizzas, id: \.self)

You are using this form of the ForEach construct:

init(
    _ data: Data,
    id: KeyPath<Data.Element, ID>, 
    content: @escaping (Data.Element) -> Content
)

in which the generic parameter ID has to conform the the Hashable protocol.

By using \.self there, you are asserting that the struct referenced under model.pizzas conforms to Hashable. This is almost certainly not the case.

That struct does, however, conform to Identifiable (i.e., it has an id property), so you can do this:

ForEach(model.pizzas)

As you found out, the image property works there as an id. This is because UIImage inherits from NSObject, which does conform to Hashable. So a UIImage can be used as a legit id in ForEach.

As for why

ForEach(r.toppings, id: \.self)

needs the id parameter, it’s because toppings is an array of Strings. String does not conform to Identifiable (i.e., it does not have an id property) so you can’t do this:

ForEach(r.toppings)

but it does conform to Hashable, so it can be used in the id parameter of a ForEach.

Basically, SwiftUI needs a way to uniquely identify the individual elements in a ForEach. This can be done by making those elements Identifiable (i.e., giving them an id property) or by referencing a Hashable property (or even the element itself if that is Hashable) in the id parameter.

Make sense?

1 Like

I’ve read it a few times, and I’ve learned a lot. However, I have some follow-up questions. I appreciate it if you can offer further explanation.

You mentioned that structs conform to Identifiable protocol, but strings don’t. Is it in general or in this particular case?

Is there a way to check whether model.pizzas struct conforms to Hashable in SwiftUI?

I am getting the impression that the rule of thumb is always making sure that everything in a ForEach loop is identifiable or with a unique id, and if things are already identifiable, there is no need to add an id parameter. Is this impression right?

Structs can conform to Identifiable but don’t necessarily do so. You have to satisfy the requirements of the protocol, which is to provide an id property that is Hashable, and then declare your conformance like so:

struct Something: Identifiable {
    let id: UUID //or String or Int or whatever you want to use as the id
    //blah blah blah
}

Strings don’t conform to Identifiable because they have no id property. You could add one by adding Identifiable conformance yourself (i.e., by giving it an id property), but conforming types you don’t own to protocols you don’t own is a bad idea.

If you see something like this:

struct Pizza: Hashable {
    //blah blah blah
}

then it’s Hashable.

Yes, it’s a good idea, but sometimes it’s not possible, as you can see when using r.toppings in a ForEach. That’s when you have to use the ForEach(_:id:content:) form and explicitly indicate what to use to uniquely identify each item in the collection, whether that be a different non-id property or the thing itself (such as when you use \.self).

1 Like

Thank you for the explanation. I learned a lot.