SwiftUI MVVM:更新父视图时重新初始化子视图模型

nde*_*nde 11 ios swift swiftui

我正在尝试在 SwiftUI 应用程序中使用 MVVM,但是似乎每当父子视图都更新时,子视图的视图模型(例如 a 中的视图模型NavigationLink)会重新初始化ObservableObject。这会导致孩子的本地状态被重置,网络数据被重新加载等。

我猜这是因为这会导致body重新评估parent 的值,其中包含一个 toSubView的视图模型的构造函数,但我一直无法找到替代方法来让我创建不会超出生命周期的视图模型看法。我需要能够将数据从父视图传递给子视图模型。

这是我们试图完成的一个非常简化的操场,在那里递增EnvCounter.counterresets SubView.counter

import SwiftUI
import PlaygroundSupport

class EnvCounter: ObservableObject {
    @Published var counter = 0
}

struct ContentView: View {
    @ObservedObject var envCounter = EnvCounter()

    var body: some View {
        VStack {
            Text("Parent view")
            Button(action: { self.envCounter.counter += 1 }) {
                Text("EnvCounter is at \(self.envCounter.counter)")
            }
            .padding(.bottom, 40)

            SubView(viewModel: .init())
        }
        .environmentObject(envCounter)
    }
}

struct SubView: View {
    class ViewModel: ObservableObject {
        @Published var counter = 0
    }

    @EnvironmentObject var envCounter: EnvCounter
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        VStack {
            Text("Sub view")

            Button(action: { self.viewModel.counter += 1 }) {
                Text("SubView counter is at \(self.viewModel.counter)")
            }

            Button(action: { self.envCounter.counter += 1 }) {
                Text("EnvCounter is at \(self.envCounter.counter)")
            }
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView())
Run Code Online (Sandbox Code Playgroud)

小智 7

一个新的属性包装被添加到SwiftUI在Xcode 12 @StateObject。您应该能够通过简单地改变来解决它@ObservedObject@StateObject如下。

struct SubView: View {
    class ViewModel: ObservableObject {
        @Published var counter = 0
    }

    @EnvironmentObject var envCounter: EnvCounter
    @StateObject var viewModel: ViewModel // change on this line

    var body: some View {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这并没有提供问题的答案。一旦您拥有足够的[声誉](https://stackoverflow.com/help/whats-reputation),您将能够[对任何帖子发表评论](https://stackoverflow.com/help/privileges/comment);相反,[提供不需要询问者澄清的答案](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-c​​an-我-做-相反)。- [来自评论](/review/low-quality-posts/26718441) (3认同)

Han*_*ach 5

为了解决这个问题,我创建了一个名为ViewModelProvider.

提供程序为您的视图获取一个哈希值,以及一个构建 ViewModel 的方法。然后它要么返回 ViewModel,要么在它第一次收到该散列时构建它。

只要您确保只要您想要相同的 ViewModel,哈希值就保持不变,这就解决了问题。

class ViewModelProvider {
    private static var viewModelStore = [String:Any]()
    
    static func makeViewModel<VM>(forHash hash: String, usingBuilder builder: () -> VM) -> VM {
        if let vm = viewModelStore[hash] as? VM {
            return vm
        } else {
            let vm = builder()
            viewModelStore[hash] = vm
            return vm
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在您的视图中,您可以使用 ViewModel:

Struct MyView: View {
    @ObservedObject var viewModel: MyViewModel
    
    public init(thisParameterDoesntChangeVM: String, thisParameterChangesVM: String) {
        self.viewModel = ViewModelProvider.makeViewModel(forHash: thisParameterChangesVM) {
            MOFOnboardingFlowViewModel(
                pages: pages,
                baseStyleConfig: style,
                buttonConfig: buttonConfig,
                onFinish: onFinish
            )
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,有两个参数。仅thisParameterChangesVM用于散列。这意味着即使thisParameterDoesntChangeVM更改并重新构建视图,视图模型也保持不变。