Ios SwiftUI MVVM:在更新父视图时重新初始化子视图模型
我正在尝试在SwiftUI应用程序中使用MVVM,但是,每当父视图和子视图都观察到的Ios SwiftUI MVVM:在更新父视图时重新初始化子视图模型,ios,swift,swiftui,Ios,Swift,Swiftui,我正在尝试在SwiftUI应用程序中使用MVVM,但是,每当父视图和子视图都观察到的可观察对象更新时,子视图的视图模型(例如NavigationLink中的视图模型)就会重新初始化。这会导致重置孩子的本地状态,重新加载网络数据等 我猜这是因为这会导致重新计算父视图的主体,它包含子视图视图模型的构造函数,但我还无法找到一个替代方法,让我创建的视图模型不会超出视图的生命周期。我需要能够将数据从父视图传递到子视图模型 这里是一个非常简单的游戏场,介绍了我们试图实现的功能,其中递增EnvCounter.
可观察对象更新时,子视图的视图模型(例如NavigationLink
中的视图模型)就会重新初始化。这会导致重置孩子的本地状态,重新加载网络数据等
我猜这是因为这会导致重新计算父视图的主体
,它包含子视图
视图模型的构造函数,但我还无法找到一个替代方法,让我创建的视图模型不会超出视图的生命周期。我需要能够将数据从父视图传递到子视图模型
这里是一个非常简单的游戏场,介绍了我们试图实现的功能,其中递增EnvCounter.counter
重置子视图.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())
我也遇到了同样的问题,你的猜测是对的,SwiftUI每次状态改变时都会计算所有父实体。解决方案是将子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)
}
}
在Xcode 12中,
@StateObject
向SwiftUI添加了一个新的属性包装器。您应该能够通过简单地更改@StateObject
的@ObservedObject
来修复它,如下所示
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
的自定义帮助器类
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())
提供程序为您的视图获取一个哈希值,以及一个构建ViewModel的方法。然后,它要么返回ViewModel,要么在第一次收到该散列时构建它
只要确保哈希保持不变,只要您想要相同的ViewModel,这就解决了问题
类ViewModelProvider{
私有静态var viewModelStore=[String:Any]()
静态func makeViewModel(forHash散列:String,usingBuilder:()->VM)->VM{
如果让vm=viewModelStore[hash]作为?vm{
返回虚拟机
}否则{
让vm=builder()
viewModelStore[hash]=vm
返回虚拟机
}
}
}
然后在视图中,可以使用ViewModel:
Struct MyView:View{
@ObservedObject var viewModel:MyViewModel
public init(thisParameterDoesntChangeVM:String,thisParameterChangesVM:String){
self.viewModel=ViewModelProvider.makeViewModel(forHash:this参数changesvm){
MoFoonboardingFlowViewModel(
页数:页数,
baseStyleConfig:style,
buttonConfig:buttonConfig,
onFinish:onFinish
)
}
}
}
在本例中,有两个参数。哈希中仅使用
此参数changesvm
。这意味着,即使此参数未更改VM
并且视图已重建,视图模型仍保持不变。这是正常行为。刷新View.body
calculable属性时,将调用该属性,因此执行内部未被内部状态条件隐藏的任何代码,从而调用所有可见的视图构造函数。只是不要在视图构造函数和/或属性默认值中放置任何繁重的内容,而是将所有这些逻辑移到视图之外(这将带来额外的好处-快速UI呈现)。这并不能回答这个问题。一旦你有足够的钱,你将能够;相反我采用了相同的方法,但没有使用[String:Any]而是使用NSMapTable(keyOptions:NSPointerFunctions.Options.strong内存,valueOptions:NSPointerFunctions.Options.weakMemory)在视图中删除viewModel。