Search code examples
swiftuicombine

SwiftUI observe published object of published object


When hitting the button, a player is added to the game and I want to observe the changes in the game through the view model. When I hit the button, the counter doesn't change.

It's almost as if I need the game within the ContentViewModel to be both @ObservedObject and @Published.

Can someone help me understand the fundamentals of why this is setup wrong and how I can fix it?

import SwiftUI
import Combine

class Game: ObservableObject {
    @Published var players: [String] = []

    func addPlayer(_ player: String) {
        players.append(player)
    }
}

class ContentViewModel: ObservableObject {
    @Published var game: Game {
        didSet {
            subscription = game.objectWillChange.sink { [weak self] _ in
                self?.objectWillChange.send()
            }
        }
    }
    var subscription: AnyCancellable?

    init(game: Game) {
        self.game = game
    }
}

struct ContentView: View {
    @ObservedObject var viewModel: ContentViewModel

    var body: some View {
        Text("Num players: \(viewModel.game.players.count)")
            .padding()

        Button("Add player") {
            viewModel.game.addPlayer("player")
        }
    }
}

Solution

  • You want to set the subscription in the init. This will make sure that every time the game object instance changes, you will trigger ContentViewModel to change as well.

    Your code doesn't work because only the object instance is mutating - not the object reference. So game won't trigger the didSet, therefore you will never set subscription.

    Code:

    class ContentViewModel: ObservableObject {
        @Published var game: Game
        var subscription: AnyCancellable?
    
        init(game: Game) {
            self.game = game
    
            subscription = game.objectWillChange.sink { [weak self] _ in
                self?.objectWillChange.send()
            }
        }
    }