How to pass data between ViewModels in SwiftUI

Viewed 1055

I have this use case where I have a parent view and a child view. Both of the views have their own corresponding ViewModels.

ParentView:

struct ParentView: View {

  @StateObject var parentViewModel = ParentViewModel()

  var body: some View {
    NavigationView {
        List {
            TextField("Add Name", text: $parentViewModel.newListName)
            NavigationLink(destination: ChildView()) {
                Label("Select Products", systemImage: K.ListIcons.productsNr)
            }
        }
    }
}

ParentViewModel:

class ParentViewModel: ObservableObject {

  @Published var newListName: String = ""

  func saveList() {
    // some logic to save to CoreData, method would be called via a button
    // how do I reference "someString" from ChildViewModel in this ViewModel?
  }
}

ChildView:

struct ChildView: View {

    @StateObject var childViewModel = ChildViewModel()

    var body: some View {
        NavigationView {
            List{
                Text("Some element")
                    .onTapGesture {
                        childViewModel.alterData()
                    }      
            }
        }
    }
}

ChildViewModel:

class ChildViewModel: ObservableObject {
    @Published var someString: String = ""

    func alterData() {
       someString = "Toast"
    }
}

My question now is, how do I pass the new value of "someString" from ChildViewModel into the ParentViewModel, in order to do some further stuff with it? I've tried to create a @StateObject var childViewModel = ChildViewModel() reference in the ParentViewModel, but that does obviously not work, as this will create a new instance of the ChildViewModel and therefore not know of the changes made to "someString"


Solution: As proposed by Josh, I went with the approach to use a single ViewModel instead of two. To achieve this, the ParentView needs a .environmentObject(T) modifier.

ParentView:

struct ParentView: View {

  @StateObject var parentViewModel = ParentViewModel()

  var body: some View {
    NavigationView {
        List {
            TextField("Add Name", text: $parentViewModel.newListName)
            NavigationLink(destination: ChildView()) {
                Label("Select Products", systemImage: K.ListIcons.productsNr)
            }
        }
    }.environmentObject(parentViewModel)
}

The ChildView then references that environment Object via @EnvironmentObject without an initializer:

struct ChildView: View {

    @EnvironmentObject var parentViewModel: ParentViewModel

    var body: some View {
        NavigationView {
            List{
                Text("Some element")
                    .onTapGesture {
                        parentViewModel.alterData()
                    }      
            }
        }
    }
}
1 Answers

Most likely you would use a binding for this situation:

struct ChildView: View {
    @Binding var name: String

    var body: some View {
        NavigationView {
            List{
                Text("Some element")
                    .onTapGesture {
                        name = "Altered!"
                    }      
            }
        }
    }
}

And in the parent:

struct ParentView: View {

  @StateObject var parentViewModel = ParentViewModel()

  var body: some View {
    NavigationView {
        List {
            TextField("Add Name", text: $parentViewModel.newListName)
            NavigationLink(destination: ChildView(name: $parentViewModel.newListName)) {
                Label("Select Products", systemImage: K.ListIcons.productsNr)
            }
        }
    }
}

Also, I think you can remove the NavigationView view from ChildView. Having it ParentView is enough.

Related