How to share published model between two view models in SwiftUI?
Asked Answered
H

1

8

I am trying to access the same shared model within two different view models. Both associated views need to access the model within the view model and need to edit the model. So I can't just use the EnvironmentObject to access the model.

I could pass the model to the view model through the view, but this wouldn't keep both model versions in sync. Is there something that could work like binding? Because with binding I can access the model but then it won't publish the changes in this view.

Simplified Example:

First view in NavigationView with adjacent view two:

struct ContentView1: View {
    @StateObject var contentView1Model = ContentView1Model()
    var body: some View {
        NavigationView {
            VStack{
                TextField("ModelName", text: $contentView1Model.model.name)
                NavigationLink(destination: ContentView2(model: contentView1Model.model)){
                    Text("ToContentView2")
                }
            }
        }
    }
}

class ContentView1Model: ObservableObject {
    @Published var model = Model()
    //Some methods that modify the model
}

Adjacent view 2 which needs access to Model:

struct ContentView2: View {
    @StateObject var contentView2Model: ContentView2Model
    
    init(model: Model) {
        self._contentView2Model = StateObject(wrappedValue: ContentView2Model(model: model))
    }
    
    var body: some View {
        TextField("ModelName", text: $contentView2Model.model.name)
    }
}

class ContentView2Model: ObservableObject {
    
    @Published var model: Model // Tried binding but this won't publish the changes.
    
    init(model: Model) {
        self.model = model
    }
}

Model:

struct Model {
    var name = ""
}

Thanks for the help!

Hinkley answered 30/12, 2020 at 9:55 Comment(2)
What is Model?Gym
Thanks, in the example It's just this name. But in the real use case it would be a more complex struct.Hinkley
G
6

Ok, Model is struct, so it is just copied when you pass it from ContentViewModeltoContentView2Model` via

ContentView2(model: contentView1Model.model)

This is the case when it is more preferable to have model as standalone ObservableObject, so it will be passed by reference from one view model into another.

class Model: ObservableObject {
    @Published var name = ""
}

and then you can inject it and modify in any needed subview, like

struct ContentView1: View {
    @StateObject var contentView1Model = ContentView1Model()
    var body: some View {
        NavigationView {
            VStack{
                ModelEditView(model: contentView1Model.model)       // << !!
                NavigationLink(destination: ContentView2(model: contentView1Model.model)){
                    Text("ToContentView2")
                }
            }
        }
    }
}

struct ContentView2: View {
    @StateObject var contentView2Model: ContentView2Model
    
    init(model: Model) {
        self._contentView2Model = StateObject(wrappedValue: ContentView2Model(model: model))
    }
    
    var body: some View {
        ModelEditView(model: contentView2Model.model)       // << !!
    }
}

struct ModelEditView: View {
    @ObservedObject var model: Model
    
    var body: some View {
        TextField("ModelName", text: $model.name)
    }
}
Gym answered 30/12, 2020 at 10:10 Comment(4)
So far so good, but how could I publish the changes made by the observed object inside the ModelEditView view to the "parent view"? E.g. if there is a text view in ContentView1 wich accesses the name stored in the model. The underlying model is changed by the ModelEditView, but it is not publishing the changes to the parent view. THXHinkley
Edit: Found a solution. Since swift is currently not supporting nested ObservableObjects you need to pass the objectWillChange Publisher to the parent Object. For details have a look at this: #58406787Hinkley
@Gym what if I want to keep the Model as a struct and don't want to publish the changes through ObservableObject, how to do that with struct ? giving that classes are less performant than structsHaycock
@JAHelia, then I would inject contentView1Model as environmentObject into root view hierarchy and access it in whatever child needed via ObservedObject wrapper to work with same Model value.Gym

© 2022 - 2024 — McMap. All rights reserved.