简体   繁体   中英

SwiftUI not updating ObservedObject in child-view

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

    }
}

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

    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM