0

So I tried to learn SwiftUI from Stanford CS193p. This works great, however, I can't get my head around why this is not working. I have the same exact view as the instructor has:

struct ContentView: View {

    @ObservedObject var viewModel: EmojiMemoryGame

    var body: some View {
        HStack {
            ForEach(self.viewModel.cards) { card in
                CardView(card: card).onTapGesture {
                    self.viewModel.chooseCard(card: card)
                }
            }
            .aspectRatio(2/3, contentMode: .fit)
        }
            .foregroundColor(.orange)
            .padding()
            .font(viewModel.numberOfPairsOfCards >= 5 ? .callout : .largeTitle)
    }
}

struct CardView: View {

    var card: MemoryGame<String>.Card

    var body: some View {
        VStack {
            ZStack {
                if card.isFaceUp {
                    RoundedRectangle(cornerRadius: 10.0).fill(Color.white)
                    RoundedRectangle(cornerRadius: 10.0).stroke(lineWidth: 3)
                    Text(card.content)
                } else {
                    RoundedRectangle(cornerRadius: 10.0).fill(Color.orange)
                }
            }
        }
    }
}

The issue is that this does not update the view, it's as if the published information from the model does not get passed down the hierarchy. I know it works since if I change the code to this:

struct ContentView: View {

    @ObservedObject var viewModel: EmojiMemoryGame

    var body: some View {
        HStack {
            ForEach(self.viewModel.cards) { card in
                ZStack {
                    if card.isFaceUp {
                        RoundedRectangle(cornerRadius: 10.0).fill(Color.white)
                        RoundedRectangle(cornerRadius: 10.0).stroke(lineWidth: 3)
                        Text(card.content)
                    } else {
                        RoundedRectangle(cornerRadius: 10.0).fill(Color.orange)
                    }
                }
                    .onTapGesture {
                        self.viewModel.chooseCard(card: card)
                    }
            }
            .aspectRatio(2/3, contentMode: .fit)
        }
            .foregroundColor(.orange)
            .padding()
            .font(viewModel.numberOfPairsOfCards >= 5 ? .callout : .largeTitle)
    }
}

all works well. All help is greatly appreciated!

class EmojiMemoryGame: ObservableObject {

    @Published private var game: MemoryGame<String> = EmojiMemoryGame.createMemoryGame()

    static private func createMemoryGame() -> MemoryGame<String> {
        let emojis = ["🎃", "👻", "🕷", "😈", "🦇"]
        return MemoryGame(numberOfPairsOfCards: Int.random(in: 2...5)) { emojis[$0] }
    }

    //MARK: - Access to the Model
    var cards: Array<MemoryGame<String>.Card> {
        game.cards
    }

    var numberOfPairsOfCards: Int {
        game.cards.count / 2
    }

    //MARK: - Intents

    func chooseCard(card: MemoryGame<String>.Card) {
        game.choose(card)
    }
}

struct MemoryGame<CardContent> {
    var cards: Array<Card>

    mutating func choose(_ card: Card) {
        if let indexOfCard = cards.firstIndex(of: card) {
            cards[indexOfCard].isFaceUp.toggle()
        }
    }

    init(numberOfPairsOfCards: Int, cardContentFactory: (Int) -> CardContent) {
        cards = Array<Card>()
        for pairIndex in 0..<numberOfPairsOfCards {
            let content = cardContentFactory(pairIndex);
            cards.append(Card(content: content, id: pairIndex * 2))
            cards.append(Card(content: content, id: pairIndex * 2 + 1))
        }
        cards.shuffle()
    }

    struct Card: Identifiable, Equatable {

        static func == (lhs: MemoryGame<CardContent>.Card, rhs: MemoryGame<CardContent>.Card) -> Bool {
            lhs.id == rhs.id
        }

        var isFaceUp = true
        var isMatched = false
        var content: CardContent
        var id: Int

    }
}

3
  • 1
    Provided code snapshot is not testable. Would you provide absent parts? Commented Jun 10, 2020 at 9:18
  • Added EmojiMemoryGame and MemoryGame Commented Jun 10, 2020 at 9:25
  • It looks like the issue is in the Card model. Cards with that have the same 'id' but are faced different are equal according to the == operator (and thus they are not updated). If I update the comparison function to include 'isFaceUp' the code appears to work. Commented Jun 10, 2020 at 10:48

1 Answer 1

1

The implementation of the equality operator in your Card struct only compares ids. The CardView is not updated because SwiftUI deduces the card hasn't changed.

Note that you may want to check for the other properties of card as well (CardContent would need to conform to Equatable).

struct Card: Identifiable, Equatable {

        static func == (lhs: MemoryGame<CardContent>.Card, rhs: MemoryGame<CardContent>.Card) -> Bool {
            return lhs.id == rhs.id && lhs.isFaceUp == rhs.isFaceUp
        }

        var isFaceUp = true
        var isMatched = false
        var content: CardContent
        var id: Int

    }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.