You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 15:31:58