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!

2
  • What is Model? Commented Dec 30, 2020 at 9:57
  • Thanks, in the example It's just this name. But in the real use case it would be a more complex struct. Commented Dec 30, 2020 at 10:02

1 Answer 1

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)
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

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. THX
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: stackoverflow.com/questions/58406287/…
@Asperi 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 structs
@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.

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.