Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios SwiftUI MVVM:在更新父视图时重新初始化子视图模型_Ios_Swift_Swiftui - Fatal编程技术网

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

Ios SwiftUI MVVM:在更新父视图时重新初始化子视图模型,ios,swift,swiftui,Ios,Swift,Swiftui,我正在尝试在SwiftUI应用程序中使用MVVM,但是,每当父视图和子视图都观察到的可观察对象更新时,子视图的视图模型(例如NavigationLink中的视图模型)就会重新初始化。这会导致重置孩子的本地状态,重新加载网络数据等 我猜这是因为这会导致重新计算父视图的主体,它包含子视图视图模型的构造函数,但我还无法找到一个替代方法,让我创建的视图模型不会超出视图的生命周期。我需要能够将数据从父视图传递到子视图模型 这里是一个非常简单的游戏场,介绍了我们试图实现的功能,其中递增EnvCounter.

我正在尝试在SwiftUI应用程序中使用MVVM,但是,每当父视图和子视图都观察到的
可观察对象更新时,子视图的视图模型(例如
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。