SwiftUI 绑定CoreData的列表重排序后页面自动消失问题
CoreData列表拖拽重排序后视图自动dismiss的解决方案
问题根因
ForEach使用了不稳定的标识作为元素id是触发dismiss的核心原因:
- 你当前的
ForEach(days, id: \.self )使用CoreData对象本身作为元素标识,而你在重排序逻辑中修改了每个Days对象的id属性,直接导致所有元素的哈希值、身份标识发生变更 @FetchRequest会监听Days实体的所有变更,id属性修改后会立刻触发days结果集刷新,SwiftUI会判定整个列表的元素全部被替换,进而触发DaysOrderView的完整重建。如果这个视图是通过Sheet、NavigationLink等方式跳转展示的,重建过程就会触发自动dismiss。
修复步骤
- 选用稳定的元素标识,不要用可变的属性或者对象本身作为ForEach的id:可以直接使用CoreData托管对象自带的稳定唯一标识
objectID,不需要额外修改CoreData模型。 - 重排序完成后手动保存ManagedObjectContext,避免修改仅停留在内存中,重启App后丢失。
- 可选优化:仅更新受移动操作影响的元素的排序id,不需要全量遍历赋值,可提升大数据量下的性能。
修复后代码
import SwiftUI struct DaysOrderView: View { @Environment(\.managedObjectContext) var moc @FetchRequest(entity: Days.entity(), sortDescriptors:[NSSortDescriptor(key: "id", ascending: true)]) var days : FetchedResults<Days> var body: some View { List { // 改用CoreData自带的稳定objectID作为元素标识 ForEach(days, id: \.objectID ) { day in HStack { Image(systemName: day.isDisplayed.boolValue ? "checkmark.circle.fill" : "checkmark.circle").foregroundColor(.yellow) Text(day.name) } } .onMove(perform: onMove) } .toolbar { EditButton() } } private func onMove(source: IndexSet, destination: Int) { var revisedItems: [ Days ] = days.map{ $0 } revisedItems.move(fromOffsets: source, toOffset: destination ) for reverseIndex in stride( from: revisedItems.count - 1, through: 0, by: -1 ) { revisedItems[ reverseIndex ].id = Int16( reverseIndex ) } // 持久化修改到CoreData try? moc.save() } }
如果你需要自行维护唯一标识,也可以给
Days实体新增一个UUID类型的uuid属性,设置默认值为UUID(),后续永远不修改这个属性,把ForEach的id改为\.uuid即可,效果和使用objectID一致。
内容的提问来源于stack exchange,提问作者kondiko21
相关产品推荐
相关产品推荐

