首页 > 解决方案 > 订阅环境视图模型的子对象内的值更改(发生这种情况时不会重新渲染视图)

问题描述

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

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其值发生更改,将导致视图按预期重新呈现。但是,例如,如果 currentScreen@Published属性发生NavigationViewModel更改,则视图不会被重新渲染。

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

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

标签: iosswiftswiftuiviewmodelcombine

解决方案


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

选项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 }
}

您当然也可以在 any 中调用它View

struct ContentView: View {

    @StateObject var navigationModel = NavigationModel.shared
}

您可能需要objectWillChange.send()在更改发布者后致电。

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

推荐阅读