5

I'm creating a new watchOS app using SwiftUI and Combine trying to use a MVVM architecture, but when my viewModel changes, I can't seem to get a Text view to update in my View.

I'm using watchOS 6, SwiftUI and Combine. I am using @ObservedObject and @Published when I believe they should be used, but changes aren't reflected like I would expect.

// Simple ContentView that will push the next view on the navigation stack
struct ContentView: View {
    var body: some View {
        NavigationLink(destination: NewView()) {
            Text("Click Here")
        }
    }
}

struct NewView: View {
    @ObservedObject var viewModel: ViewModel

    init() {
        viewModel = ViewModel()
    }

    var body: some View {
        // This value never updates
        Text(viewModel.str)
    }
}

class ViewModel: NSObject, ObservableObject {
    @Published var str = ""
    var count = 0

    override init() {
        super.init()

        // Just something that will cause a property to update in the viewModel
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.count += 1
            self?.str = "\(String(describing: self?.count))"

            print("Updated count: \(String(describing: self?.count))")
        }
    }
}

Text(viewModel.str) never updates, even though the viewModel is incrementing a new value ever 1.0s. I have tried objectWillChange.send() when the property updates, but nothing works.

Am I doing something completely wrong?

lepolt
  • 501
  • 5
  • 13
  • 1
    Well that should work unless your `Timer` is deallocated prematurely. Keep a strong reference to the timer object and that should do the trick. [See this](https://stackoverflow.com/a/48689125/3687801) – nayem Oct 04 '19 at 02:26
  • The Timer is not going away. It will print to the console indefinitely in this example. – lepolt Oct 04 '19 at 10:52

1 Answers1

4

For the time being, there is a solution that I luckily found out just by experimenting. I'm yet to find out what the actual reason behind this. Until then, you just don't inherit from NSObject and everything should work fine.

class ViewModel: ObservableObject {
    @Published var str = ""
    var count = 0

    init() {

        // Just something that will cause a property to update in the viewModel
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.count += 1
            self?.str = "\(String(describing: self?.count))"

            print("Updated count: \(String(describing: self?.count))")
        }
    }
}

I've tested this and it works.


A similar question also addresses this issue of the publisher object being a subclass of NSObject. So you may need to rethink if you really need an NSObject subclass or not. If you can't get away from NSObject, I recommend you try one of the solutions from the linked question.

nayem
  • 7,285
  • 1
  • 33
  • 51
  • Wow. That fixed my example! Thank you. – lepolt Oct 04 '19 at 10:53
  • I had a similar problem. In my case, I hit a webservice onAppear of view & I edit the attributes based on the response from service. I see the ViewModel's attributes are being updated with new values but the view doesn't get updated. My VieModel doesn't subclass from NSObject either. MyViewModel has an attribute which is an instance of an Observable object. Are there any other considerations that I missed? – Dinakar Apr 18 '20 at 13:25