CS193 view doesnt update with model(MVVM)

Viewed 64

I've been following this Stanford course on swiftUI and I've gotten to a point where im 100% sure my code is essentially identical to what i'm following but it doesn't work, I've been trying to debug it but for some reason, that I don't understand the view doesn't update.

view-model:

class CardModel: ObservableObject
{
    // view model
    static var items = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",]
    static var count = 3
    
    static func createMemoryGame() -> MemoryGame<String>
    {
        MemoryGame<String>(numberOfPairsOfCards: CardModel.count, createCardContent:
        {
            pairIndex in
            CardModel.items[pairIndex]
        })
    }
    
    @Published private var model: MemoryGame<String> = CardModel.createMemoryGame()
    
    var cards: Array<MemoryGame<String>.Card>
    {
        return model.cards
    }
    
    // MARKS: - Intent(s)
    
    func choose(_ card: MemoryGame<String>.Card)
    {
        print("debug func choose in cardmodel")
        model.choose(card)
    }
    
}

view:

struct ContentView: View{
    @ObservedObject var viewModel: CardModel
    
    var body: some View
    {
        VStack
        {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 75))])
            {
                ForEach(viewModel.cards)
                {
                    card in
                    CardView(card: card).aspectRatio(2/3, contentMode: .fit)
                        .onTapGesture {
                            print("\(card.isFaceUp)")
                            viewModel.choose(card)
                            print("\(card.isFaceUp)")
                        }
                }
            }
            Spacer()
            .foregroundColor(.red)
        }
        .padding(.horizontal)
        .font(.largeTitle)
    }
}

model:

struct MemoryGame<CardContent>
{
    private(set) var cards: Array<Card>
    
    mutating func choose(_ card: Card)
    {
        let chosenIndex = index(of: card)
        cards[chosenIndex].isFaceUp = !cards[chosenIndex].isFaceUp
        print("debug func choose in memorygame")
    }
    
    func index(of card: Card) -> Int
    {
        for index in 0..<cards.count
        {
            if cards[index].id == card.id
            {
                print("debug func index")
                return index
            }
        }
        return 0
    }

when I uncomment all the debug comments the output is

-true -debug func choose in cardmodel -debug func index -debug func choose in memorygame -true

https://www.youtube.com/watch?v=oWZOFSYS5GE heres the video I'm stuck on all my code passes all the same debug tests up until 32:48

1 Answers

Sadly the CS193 Stanford course is teaching SwiftUI totally wrong. I recommend Apple's samples and WWDC videos to learn the correct structure. MMVM is a big mistake, the View struct and the property wrappers is everything you need. The reason is the View struct is already stores the data that SwiftUI takes and diffs and then creates UIViews/NSViews on our behalf. Furthermore, the property wrappers like @State and @Binding make the efficient struct behave like an object. So there is your view model object right there. If you use an actual object as well, then you'll experience the kind of consistency bugs that SwiftUI was designed to eliminate.

To fix the code you shared, try something like this:

    class CardStore: ObservableObject
    {
        @Published var cards: [Card] = []
    
        static var shared = CardStore()
    
        static var preview = CardStore(preview: true)
    
        init(preview: Bool = false) {
            // create initial cards array depending on preview or not
        }

        // other funcs that can work on Cards, usually looking up by their ID (from Identifiable).
@main
struct CardGame: App {
    let store = CardStore.shared

    var body: some Scene { 
        WindowGroup {
            ContentView()
                .environmentObject(store)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var store: CardStore

   var body: some View {
       ForEach($card in store.cards) { $card in
           CardView(card: $card)
       }
   }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(CardStore.preview)
    }
}

struct CardView: View {
    @Binding var card: Card

    var body: some View {
        FaceUpView(isFaceUp: $card.isFaceUp)
    }
}

struct FaceUpView {
    @Binding var isFaceUp: Bool

    var body: some View {
        // here you have write access to isFaceUp, in the card, from the store.
    }
}
Related