2

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)
    }
}
Steven-Carrot
  • 2,368
  • 2
  • 12
  • 37
ice-wind
  • 690
  • 4
  • 20
  • 1
    Can you show _how_ you are using your struct in `ForEach`? – Sweeper Oct 22 '21 at 17:02
  • 3
    I hope you are not doing `id: \.self` in your ForEach – Joakim Danielson Oct 22 '21 at 17:19
  • ^ Bingo! I think you have it! – Yrb Oct 22 '21 at 17:26
  • I am not using \.self, adding the foreach code to the post... – ice-wind Oct 23 '21 at 14:13
  • What is your `CardContent`? I suspect it needs to be `Identifiable` as well. – Yrb Oct 23 '21 at 17:15
  • @Yrb I have added every line of code of the project to the post (safe for CardView which I think is irrelevant to the issue at hand) – ice-wind Oct 24 '21 at 09:18
  • The reason it is important is this: [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). I found someone else's `CardView` and put the app together. I see no errors. I get a grid of emoji pairs when it runs. This may be a phantom error. Clean your build folder, delete the apps folder in Derived Data and rebuild it. See if that works. – Yrb Oct 24 '21 at 18:48
  • Post may be dated, but wondering why using id: \.self would know an error? – Swink Jun 07 '22 at 12:59

2 Answers2

1

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) { }
Steven-Carrot
  • 2,368
  • 2
  • 12
  • 37
  • Honestly I'm way past that point right now, I think it's just the random kinda errors Xcode like to throw sometimes – ice-wind Aug 05 '22 at 17:57
-1

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)
        }
    }
Alexis
  • 1