LazyVGrid未懒加载:20个ViewModel被立即创建问题咨询
问题
我用SwiftUI写了如下代码,创建了包含20个RMCharacterViewModel的列表,界面每次仅显示6个单元格,但所有20个ViewModel却被立即创建。我原本预期仅会创建6个单元格并下载对应图片,请问这是什么原因?
struct ContentView: View { @StateObject var vm = RMCharacterListResponseDownloadViewModel() let columns = Array(repeating: GridItem(.flexible(),spacing: 1, alignment: nil), count: 2) var body: some View { ScrollView { LazyVGrid(columns: columns) { ForEach(vm.items, id: \.self) { item in RMCharacterView(viewModel: item) } } .padding(1) } } } struct RMCharacterView: View { let viewModel: RMCharacterViewModel private let imageWidth = 110.0 private let cellHeight = 330.0 var body: some View { CacheAsyncImage( url: viewModel.characterImageURL ) { phase in switch phase { case .success(let image): VStack { CharacterCard(characterImage: image) CharacterDescriptionView(viewModel: viewModel) } case .failure(let error): ErrorView(error: error) case .empty: VStack { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .red)) Spacer() } @unknown default: // AsyncImagePhase is not marked as @frozen. // We need to support new cases in the future. Image(systemName: "questionmark") } } } }
原因及解决方法
核心原因
LazyVGrid的懒加载只作用于View的实例化与渲染,不会控制你提前准备好的ViewModel对象的创建时机。你当前的vm.items是提前把20个RMCharacterViewModel全部初始化完成的数组,所以只要这个数组被创建,所有ViewModel就会立刻存在,和LazyVGrid是否渲染对应的Cell无关。
如果你的CacheAsyncImage是在ViewModel初始化时就触发了图片下载逻辑(比如ViewModel内部直接发起网络请求),那所有图片也会被同时触发下载;如果只是在CacheAsyncImage的body里访问URL才发起请求,那图片下载会和Cell渲染同步,但ViewModel本身还是会全部提前创建。
解决思路
不要提前创建所有ViewModel,而是把原始数据(比如RMCharacter模型对象)存在vm.items里,在RMCharacterView内部按需初始化ViewModel:
- 修改
RMCharacterListResponseDownloadViewModel的items类型为原始模型数组(比如[RMCharacter]) - 修改
RMCharacterView的参数为原始模型,在View内部创建ViewModel:
struct RMCharacterView: View { private let character: RMCharacter private var viewModel: RMCharacterViewModel { RMCharacterViewModel(character: character) } // ... 其余代码不变 }
或者在View初始化时懒加载ViewModel:
struct RMCharacterView: View { private let character: RMCharacter private lazy var viewModel: RMCharacterViewModel = RMCharacterViewModel(character: character) // ... 其余代码不变 }
这样只有当LazyVGrid渲染某个Cell时,对应的RMCharacterView才会被创建,ViewModel也才会随之初始化,图片下载也会同步延迟到Cell可见时触发。
内容的提问来源于stack exchange,提问作者Craig
相关产品推荐
相关产品推荐

