简体   繁体   中英

SwiftUI: Communication between different ViewModels

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!

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)
    }
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM