SwiftUI:宿主View状态变化时如何触发@StateObject ViewModel更新
问题场景
我有一个用于展示图片网格的View,该View通过@StateObject声明持有对应的ViewModel实例,同时持有存储图片数据的状态变量:图片索引存储在Core Data中,使用的自定义@Query属性包装器负责存储Core Data的拉取结果,当结果发生变更时,会和@State变量一样驱动View刷新。
当Core Data拉取结果更新时,我需要ViewModel感知到该变更,重建索引数据供ForEach渲染使用,但目前没有找到关联两个变更事件的方法。我已经通过点击手势手动触发ViewModel刷新验证了indexAssets确实会正常更新,现在需要实现indexAssets变化时自动触发ViewModel刷新的效果。
现有代码
PhotoGridView.swift
struct PhotoGridView: View { @Query(.all) var indexAssets: QueryResults<IndexAsset> @StateObject var vm = PhotoGridViewModel() func updateVm() { vm.createIndex(indexAssets) } var body: some View { return GeometryReader { geo in VStack { HStack { Text("\(indexAssets.count) assets") Spacer() TrashView() }.padding(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)) ScrollView { ForEach(vm.sortedKeys, id: \.self) { key in let indexAssets = vm.index[key] let date = indexAssets?.first?.creationDate GridSectionView(titleDate:date, indexAssets:$vm.index[key], geoSize: geo.size) } } }.onAppear { updateVm() }.onTapGesture { // 测试用手动触发,已验证逻辑正常 updateVm() } .navigationDestination(for: IndexAsset.self) { indexAsset in AssetDetailView(indexAsset: indexAsset) } } } }
PhotoGridViewModel.swift
class PhotoGridViewModel: ObservableObject { @Published var index: [String:[IndexAsset]] = [:] @Published var indexAssets: QueryResults<IndexAsset>? @Published var sortedKeys : [String] = [] func createIndex() { guard let assets = self.indexAssets else {return} self.createIndex(assets) } func createIndex(_ queryResults: QueryResults<IndexAsset>) -> [String:[IndexAsset]]? { indexAssets = queryResults if queryResults.count > 0 { var lastDate = Date.distantFuture var newIndex = [String:[IndexAsset]]() for i in 0..<queryResults.count { let item = queryResults[i] let isSameDay = isSameDay(firstDate: lastDate, secondDate: item.creationDate!) if isSameDay { newIndex[item.creationDateKey!]?.append(item) } else { newIndex[item.creationDateKey!] = [item] } lastDate = item.creationDate! } self.index = newIndex self.sortedKeys = newIndex.keys.sorted().reversed() return newIndex } return nil } private func isSameDay(firstDate:Date, secondDate:Date) -> Bool { return Calendar.current.isDate( firstDate, equalTo: secondDate, toGranularity: .day ) } }
说明:未附上
@Query的实现代码,其核心逻辑为存储Core Data的最新拉取结果,结果变化时会像@State变量一样触发View更新。
实现方案
直接使用SwiftUI内置的onChange修饰符监听indexAssets的变更即可,不需要手动触发刷新。
修改PhotoGridView中VStack的挂载修饰符,移除测试用的onTapGesture,添加变化监听逻辑:
}.onAppear { updateVm() } .onChange(of: indexAssets) { newAssets in vm.createIndex(newAssets) } .navigationDestination(for: IndexAsset.self) { indexAsset in AssetDetailView(indexAsset: indexAsset) }
如果项目最低支持版本为iOS 14/15,onChange闭包不直接返回新值,使用如下兼容写法:
.onChange(of: indexAssets) { _ in updateVm() }
@Query触发视图刷新时,onChange会自动对比indexAssets的前后值,检测到变更就会执行闭包内的索引重建逻辑,可覆盖Core Data数据新增、删除、修改的全量更新场景。
内容的提问来源于stack exchange,提问作者Jake Z
相关产品推荐
相关产品推荐

