0

The code below does not update when I call viewModel.updateToBanana(). How can I update the view when I set model.title to Banana.
This is my code:

class MyModel: ObservableObject {
    @Published var title: String = ""
    
    init(title: String) {
        self.title = title
    }
}

class MyViewModel: ObservableObject {
    @Published var model = MyModel(title: "Apple")

    func updateToBanana() {
        model.title = "Banana"
    }
    
}

struct MyView: View {
    @StateObject var viewModel = MyViewModel()
    
    var body: some View {
        VStack(spacing: 20) {
            Text(viewModel.model.title)            
            Button {
                viewModel.updateToBanana()
            } label: {
                Text("Banana")
            }
        }
    }
}

I have tried to change @Published to @ObservedObject, but it did not work.

4
  • There is no function named updateToBanana in your example, suggest renaming the function update or correcting the function call. Commented Nov 3, 2024 at 13:28
  • Every ObservableObject needs a property wrapper in a View. Any solution that forces the parent model to refresh is terribly inefficient. Commented Nov 3, 2024 at 21:18
  • An alternative approach to achieve what you want, is to use the more modern Observation framework. Then you can nest the observed classes. See Managing model data. If you want to keep the older system, other ways to use nested ObservableObject classes, see stackoverflow.com/questions/58406287/… Commented Nov 4, 2024 at 4:14
  • Nested objects are a very common problem that can be avoided by using a struct, see here: stackoverflow.com/questions/68400530/… Commented Nov 4, 2024 at 17:44

2 Answers 2

0

The reason why the update doesn't trigger a change to the view is because viewModel does not send an update when the underlying model is changed. This is because, MyModel is a class (which it has to be, if it implements ObservableObject) and not a struct. The same class instance is still held by MyViewModel after the update, even though its underlying property has changed.

To force a change to be published, MyViewModel could call objectWillChange.send() explicitly, before passing on the update. There is also probably not much point having model as @Published, you might as well declare it with let instead:

class MyViewModel: ObservableObject {
    let model = MyModel(title: "Apple") // 👈 not published

    func update() {
        objectWillChange.send() // 👈 added
        model.title = "Banana"
    }
}

Another approach would be to subscribe to updates from the underlying model in your view, for example, by declaring a reference to MyModel as an @ObservedObject. However, if the model in viewModel would be replaced with a new instance, then the view might end up being subscribed to the wrong instance. So this is another reason, why it might be better if the model was declared in MyViewModel using let instead of var.

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

2 Comments

This answer works, don't know why it was downvoted. Can you explain?
@workingdogsupportUkraine Thank you, I would also be interested to know, why this answer was downvoted. I don't think it is doing anything that is particularly wrong and it addresses the fact that the instance of the underlying model can be replaced. If there would be a better solution, let's see it.
0

You can use struct and mutating func for that, .e.g.

struct MyModel {
    var title: String = ""
    
    init(title: String) {
        self.title = title
    }
}

struct MyViewModel {
    var model = MyModel(title: "Apple")

    mutating func updateToBanana() {
        model.title = "Banana"
    }
    
}

struct MyView: View {
    @State var viewModel = MyViewModel()
    
    var body: some View {
        VStack(spacing: 20) {
            Text(viewModel.model.title)            
            Button {
                viewModel.updateToBanana()
            } label: {
                Text("Banana")
            }
        }
    }
}

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.