[英]SwiftUI MVVM: child view model re-initialized when parent view updated
我正在尝试在 SwiftUI 应用程序中使用 MVVM,但是似乎每当更新父级和子级都观察到的ObservableObject
时,子视图的视图模型(例如NavigationLink
中的视图模型)都会重新初始化。 这会导致孩子的本地state被重置,网络数据被重新加载等。
我猜这是因为这会导致重新评估父母的body
,其中包含SubView
的视图 model 的构造函数,但我无法找到一个替代方案,让我创建不会超越生命的视图模型的看法。 我需要能够将数据从父视图传递给子视图 model。
这是我们试图完成的一个非常简化的操场,其中递增EnvCounter.counter
重置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())
在 Xcode 12, @StateObject
的 SwiftUI 中添加了一个新的属性包装器。 您应该能够通过简单地更改@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 {
// ...
}
}
为了解决这个问题,我创建了一个名为ViewModelProvider
的自定义助手 class 。
提供者为您的视图获取 hash,以及构建 ViewModel 的方法。 然后它要么返回 ViewModel,要么在它第一次收到 hash 时构建它。
只要您确保 hash 保持不变,只要您想要相同的 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
}
}
}
然后在您的视图中,您可以使用 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
)
}
}
}
在此示例中,有两个参数。 在thisParameterChangesVM
中仅使用了 thisParameterChangesVM。 这意味着即使thisParameterDoesntChangeVM
发生变化并且视图被重建,视图 model 保持不变。
我遇到了同样的问题,您的猜测是正确的,SwiftUI 每次 state 更改时都会计算您的所有父体。 解决方案是将子 ViewModel init 移动到父 ViewModel,这是您示例中的代码:
class EnvCounter: ObservableObject {
@Published var counter = 0
@Published var subViewViewModel = SubView.ViewModel.init()
}
struct CounterView: 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: envCounter.subViewViewModel)
}
.environmentObject(envCounter)
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.