skip to Main Content

I have a parent view which contains two child views. Each child view gets passed a different EnvironmentObject from the parent view. As a representation for all kinds of different changes, the second child view contains a Button which can be used to call a function in its ViewModel which then is supposed to change a variable in the ViewModel of the first child view.

struct ParentView: View {
   @StateObject var viewModel_1: ViewModel_1
   @StateObject var viewModel_2: ViewModel_2

   var body: some View {
       ZStack {
           ChildView_1()
               .environmentObject(viewModel_1)
        
           ChildView_2()
               .environmentObject(viewModel_2)
       }
   }}


struct ChildView_1: View {
   @EnvironmentObject var viewModel_1: ViewModel_1

   var body: some View {
       ...
   }}


struct ChildView_2: View {
   @EnvironmentObject var viewModel_2: ViewModel_2

   var body: some View {
       Button(action: {
          viewModel_2.changeValue_in_ViewModel_1(value: 1)
       }, label: {
          Text("Tap to change value")
       })
   }}


class ViewModel_1: ObservableObject {
   @Published var someValue: Int = 0

   func changeValue(value: Int) -> Void {
      self.someValue = value
   }
}


class ViewModel_2: ObservableObject {
   func changeValue_in_ViewModel_1(value: Int) -> Void {
       //something like viewModel_2.changeValue(value: value)
   }
}

Is there a way to make those two ViewModels able to communicate with each other?
Thanks!

2

Answers


  1. It would be solved simply by ViewModel_2 referencing ViewModel_1.

    However, it is not necessary to refer to all ViewModel_1, so you can separate only the desired logic using protocol and let ViewModel_2 own it.

    This is the sample code for the above explanation.

    Searching for dependency injection can yield a lot of information about it.

    
    
    struct ParentView: View {
        @StateObject var viewModel_1: ViewModel_1
        @StateObject var viewModel_2: ViewModel_2
    
        init() {
            let viewModel_1 = ViewModel_1()
            _viewModel_1 = StateObject(wrappedValue: viewModel_1)
            _viewModel_2 = StateObject(wrappedValue: ViewModel_2(changeValue: viewModel_1 as! ChangeValue))
        }
    
        var body: some View {
            VStack {
                ChildView_1()
                .environmentObject(viewModel_1)
    
                ChildView_2()
                .environmentObject(viewModel_2)
            }
        }
    }
    
    
    struct ChildView_1: View {
        @EnvironmentObject var viewModel_1: ViewModel_1
    
        var body: some View {
            Text("(viewModel_1.someValue)")
        }
    }
    
    
    struct ChildView_2: View {
        @EnvironmentObject var viewModel_2: ViewModel_2
        @State var count: Int = 0
    
        var body: some View {
            Button(action: {
                count = count + 1
                viewModel_2.changeValue_in_ViewModel_1(value: count)
            }, label: {
                Text("Tap to change value")
            })
        }
    }
    
    protocol ChangeValue {
        func changeValue(value: Int)
    }
    
    class ViewModel_1: ObservableObject, ChangeValue {
        @Published var someValue: Int = 0
    
        func changeValue(value: Int) -> Void {
            self.someValue = value
        }
    }
    
    
    class ViewModel_2: ObservableObject {
        private let changeValue: ChangeValue
    
        init (changeValue: ChangeValue) {
            self.changeValue = changeValue
        }
    
        func changeValue_in_ViewModel_1(value: Int) -> Void {
            //something like viewModel_2.changeValue(value: value)
            changeValue.changeValue(value: value)
        }
    }
    
    Login or Signup to reply.
  2. We don’t actually use view model objects in SwiftUI. The View struct is a view model already, being a value type it’s more efficient and less error prone than an object but the property wrappers make it behave like an object, SwiftUI diffs the View struct and it creates/updates actual UIView/NSViews on screen for us. If you use actual view model objects you’ll get bugs and face the problems that you are experiencing.

    You can group related @State vars into their own struct and use mutating func for logic. That way it can be tested independently but the best thing is any chance to a property of the struct is detected by SwiftUI as a change to the whole struct which makes its dependency tracking super fast.

    environmentObject is designed to hold a store object that contains the model structs (usually in arrays) in @Published properties. There isn’t usually more than one environmentObject. This object is usually responsible for persisting or syncing the model data.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search