订阅环境视图模型的子对象内的值更改(发生这种情况时视图不会被重新渲染)

Subscribing to value changes inside child objects of environment view models (view not being re-rendered when this happens)

我有一个视图模型是其他子视图模型的父视图模型。即:

public class ViewModel: ObservableObject {
    
    @Published var nav = NavigationViewModel()
    @Published var screen = ScreenViewModel()

其他子视图模型,如导航和屏幕,都有特定的用途。例如,nav 的职责是跟踪当前屏幕:

class NavigationViewModel: ObservableObject {
    
    // MARK: Publishers
    @Published var currentScreen: Screen = .Timeline
    
}

ViewModel 在 App 结构中实例化:

    @main
struct Appy_WeatherApp: App {
    
    // MARK: Global
    var viewModel = ViewModel()
    
    // MARK: -
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(viewModel)
        }
    }
}

我在任何需要访问它的视图上为它声明一个@EnvironmentObject:

@EnvironmentObject var viewModel: ViewModel 

任何引用 ViewModel 的非对象 属性 的视图 @Published 其值更改将导致视图按预期重新呈现。但是,例如,如果 NavigationViewModel 的 currentScreen @Published 属性 发生变化,则不会重新呈现视图。

我知道如果我将 NavigationViewModel 与 ViewModel 分开,我可以让它工作,在应用程序级别实例化它,并在访问其任何已发布属性的任何视图中将其用作自己的环境对象。

我的问题是上述解决方法是否真的是处理此问题的正确方法,and/or 是否有任何方法可以让视图订阅环境对象的子对象内属性的值更改?还是有另一种我没有考虑过的方法,这是我试图通过视图模型职责的碎片化来实现的推荐方法?

有几种方法可以实现这一点。

选项 1

使用 Combine.

import Combine

public class ViewModel: ObservableObject {

    @Published var nav = NavigationViewModel()
    var anyCancellable: AnyCancellable?

    init() {
        anyCancellable = nav.objectWillChange.sink { _ in
            self.objectWillChange.send()
        }
    }
}

你基本上只是在你的 navigationViewModel 发布更改时收听。如果是这样,你告诉你的观点,你的 ViewModel 也有变化。

选项 2

我想由于名称 NavigationViewModel,您会经常在其他视图模型中使用它吗?

如果是这样,我会选择单例模式,如下所示:

class NavigationViewModel: ObservableObject {

    static let shared = NavigationViewModel()
    private init() {}

    @Published var currentScreen: Screen = .Timeline     
}

在你的 ViewModel 里面:

public class ViewModel: ObservableObject {

    var nav: NavigationViewModel { NavigationViewModel.shared }
}

你当然也可以在任何 View:

中调用它
struct ContentView: View {

    @StateObject var navigationModel = NavigationModel.shared
}

您可能需要在更改发布者后调用 objectWillChange.send()

@Published var currentScreen: Screen = .Timeline {
    didSet { 
        objectWillChange.send()
    }
}