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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 20:39:21