SwiftUI:如何将View实例存入变量复用以保留ViewModel数据?
解决方案
方法一:将子ViewModel托管在父ViewModel中(推荐)
核心思路是不要存储子视图实例,而是在父ViewModel中持有子ViewModel的引用,让父视图每次重绘时复用同一个ViewModel创建子视图,从根源避免数据丢失。
修改父ViewModel
protocol ParentViewModel: ObservableObject { var childVM1: ChildViewModel { get } var childVM2: ChildViewModel { get } } class ParentViewModelImp: ParentViewModel { // 初始化时就创建好子ViewModel,后续复用 let childVM1: ChildViewModel let childVM2: ChildViewModel init() { self.childVM1 = ChildViewModelImp(list: ["a", "b", "c"]) self.childVM2 = ChildViewModelImp(list: ["x", "y", "z"]) // 如需额外配置子ViewModel,在此处处理 } }
修改父视图
struct ParentView<pViewModel>: View where pViewModel: ParentViewModel { @StateObject var vm: pViewModel var body: some View { VStack { // 复用父ViewModel中已存在的子ViewModel ChildView(vm: vm.childVM1) ChildView(vm: vm.childVM2) } } }
这种方式完全规避了some关键字带来的类型不匹配问题,同时子ViewModel的生命周期由父ViewModel管理,数据状态不会因为父视图重绘而丢失。
方法二:用AnyView统一子视图类型
如果一定要存储子视图实例,可以通过AnyView擦除具体的View类型,解决some导致的类型不一致问题。
修改父ViewModel
protocol ParentViewModel: ObservableObject { func displayChildView(isTop: Bool) -> AnyView } class ParentViewModelImp: ParentViewModel { private var childViewTop: AnyView? private var childViewBottom: AnyView? func displayChildView(isTop: Bool) -> AnyView { if isTop { if let existingView = childViewTop { return existingView } else { let vm = ChildViewModelImp(list: ["a", "b", "c"]) let newView = AnyView(ChildView(vm: vm)) childViewTop = newView return newView } } else { if let existingView = childViewBottom { return existingView } else { let vm = ChildViewModelImp(list: ["x", "y", "z"]) let newView = AnyView(ChildView(vm: vm)) childViewBottom = newView return newView } } } }
修改父视图
struct ParentView<pViewModel>: View where pViewModel: ParentViewModel { @StateObject var vm: pViewModel var body: some View { VStack { vm.displayChildView(isTop: true) vm.displayChildView(isTop: false) } } }
AnyView会包装任意View类型,让存储变量的类型统一,从而实现子视图实例的复用,避免ViewModel重复初始化。
方法三:在父视图中直接用@State存储子ViewModel
如果不需要将逻辑下沉到父ViewModel,也可以直接在父视图中用@State持有子ViewModel实例:
struct ParentView: View { // @State会保证实例在视图重绘时被保留 @State private var childVM1 = ChildViewModelImp(list: ["a", "b", "c"]) @State private var childVM2 = ChildViewModelImp(list: ["x", "y", "z"]) var body: some View { VStack { ChildView(vm: childVM1) ChildView(vm: childVM2) } } }
这种方式最简单,@State的生命周期绑定到父视图,子ViewModel不会因为父视图重绘而被重新创建,数据状态得以保留。
内容的提问来源于stack exchange,提问作者Isiah
相关产品推荐
相关产品推荐

