SwiftUI ForEach asks that items are Hashable and Equatable for some reason?

Viewed 1028

I am following the CS193P Stanford class, in which we are supposed to build a ForEach view with an array of Cards, defined in this struct :

struct MemoryGame<CardContent> {
    private(set) var cards : [Card]
    
    init(pairCount : Int, createCardContent : (Int) -> CardContent){
        cards = []
        for i in 0 ..< pairCount {
            let content = createCardContent(i)
            cards.append(Card(content : content, id : i*2))
            cards.append(Card(content : content, id : i*2 + 1))
        }
    }
    
    func choose(_ card: Card) {
        
    }
    
    struct Card : Identifiable {
        var isFaceUp : Bool = true
        var isMatched : Bool = false
        var content : CardContent
        var id: Int
    }
}

As you can see, this struct conforms to the Identifiable protocol, which according to the documentation should be enough for it to work in a ForEach (Provided the type of id is Hashable, which Int is)

Yet I get the following error :

Generic struct 'ForEach' requires that 'MemoryGame<String>.Card' conform to 'Hashable'

Why is it asking that ? The only thing the docs say is that the type of id should be Hashable, nothing about the entire struct being Hashable

The error hints also want me to add Equatable but I can't seem to understand why, there is no reported error linked to that

NB : I am building in XCode 13 on macOS Monterey 12.0.1 with iOS 15 as a target


Here is then the ViewModel code for EmojiMemoryGame :

class EmojiMemoryGame {
    static let emojis = ["", "", "", "", "", "", "", ""]
    private(set) var model = MemoryGame<String>(pairCount: 4) {i in emojis[i]}
    
    var cards: Array<MemoryGame<String>.Card> {
        model.cards
    }
}

And the ContentView :

struct ContentView: View {
    
    let viewModel : EmojiMemoryGame
    
    var body: some View {
        VStack {
            ScrollView {
                LazyVGrid(columns : [GridItem(.adaptive(minimum : 65))]) {
                    ForEach(viewModel.cards) { card in // Error happens here
                        CardView(card)
                            .aspectRatio(2/3, contentMode: .fit)
                    }
                }
            }
            .foregroundColor(.red)
        }
        .padding(.horizontal)
    }
}
2 Answers

I have implemented a var id = UUID() on the Card struct, made it conform to Identifiable and removed the id: .self from the ForEach loop. It's valid now.

   struct Card: Identifiable {
        var id = UUID()
        var isFacedUp: Bool = false
        var isMatched: Bool = false
        
        var content: CardContent
        
    }

    LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))]) {
        ForEach(viewModel.cards) { card in
            CardView(card: card)
                .aspectRatio(2/3, contentMode: .fit)
        }
    }

I copied pasted your code without modifying anything, it ran without any error.

That ForEach is not the problem here. If you still face the error, try this:

ForEach(viewModel.cards, id: \.id) { }
Related