在其他视图中更新核心数据 属性 时,SwiftUI 列表不更新

SwiftUI List not updating when core data property is updated in other view

@State var documents: [ScanDocument] = []

func loadDocuments() {
    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    
    let managedContext =
        appDelegate.persistentContainer.viewContext
    
    let fetchRequest =
        NSFetchRequest<NSManagedObject>(entityName: "ScanDocument")
    
    do {
        documents = try managedContext.fetch(fetchRequest) as! [ScanDocument]
        print(documents.compactMap({[=11=].name}))
    } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
    }
}

在第一个视图中:

.onAppear(){
     self.loadDocuments()
 }

现在我正在推动单个对象的详细视图:

NavigationLink(destination: RenameDocumentView(document: documents[selectedDocumentIndex!]), isActive: $pushActive) {
                    Text("")
                }.hidden()

在 RenameDocumentView 中:

var document: ScanDocument

此外,更新文档名称的一项功能:

func renameDocument() {
    guard !fileName.isEmpty else {return}
    document.name = fileName
    try? self.moc.save()
    print(fileName)
    self.presentationMode.wrappedValue.dismiss()
}

所有这些代码都有效。此打印语句始终打印更新值:

print(documents.compactMap({[=16=].name}))

这是主视图中的列表代码:

List(documents, id: \.id) { item in
     ZStack {
          DocumentCell(document: item)
     }
}

但是用户返回到上一屏幕的位置。该列表显示旧数据。如果我重新启动应用程序,它会显示新数据。

向新方向推进的任何帮助都会有所帮助。

这里有一个类似的问题:SwiftUI List View not updating after Core Data entity updated in another View,但没有答案。

NSManagedObject 是引用类型,因此当您更改其属性时,您的 documents 不会更改,因此状态不会刷新视图。

这是一种在您返回时强制刷新列表的可能方法

  1. 添加新状态
@State var documents: [ScanDocument] = []
@State private var refreshID = UUID()   // can be actually anything, but unique
  1. 制作由其识别的List
List(documents, id: \.id) { item in
     ZStack {
          DocumentCell(document: item)
     }
}.id(refreshID)     // << here
  1. 回来时更改 refreshID 以强制重建列表
NavigationLink(destination: RenameDocumentView(document: documents[selectedDocumentIndex!])
                               .onDisappear(perform: {self.refreshID = UUID()}), 
                isActive: $pushActive) {
                    Text("")
                }.hidden()

替代方案: 可能的替代方案是制作 DocumentCell 观察文档,但未提供代码,因此不清楚里面的内容。总之你可以试试

struct DocumentCell: View {
   @ObservedObject document: ScanDocument
 
   ...
}

核心数据批量更新不会更新内存中的对象。之后需要手动刷新。

批处理操作绕过正常的 Core Data 操作并直接在底层 SQLite 数据库(或任何支持持久存储的数据库)上操作。他们这样做是为了提高速度,但这意味着他们也不会触发您使用普通获取请求获得的所有内容。

您需要执行 Apple 的核心数据批处理编程指南中所示的操作:实施批处理更新 - 在执行后更新您的应用程序

Original answer similar case similar case

let request = NSBatchUpdateRequest(entity: ScanDocument.entity())
request.resultType = .updatedObjectIDsResultType

let result = try viewContext.execute(request) as? NSBatchUpdateResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSUpdatedObjectsKey: objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [managedContext])

尝试为该问题提供解决方案时的另一个考虑因素与类型定义和您将获取请求结果强制向下转换为 ScanDocument 对象数组(即 [ScanDocument])有关.

你的代码行...

    documents = try managedContext.fetch(fetchRequest) as! [ScanDocument]

...正在 try 强制将您的 var documents 向下转换为这种类型 - 对象数组。

事实上 NSFetchRequest 本身 return 是 NSFetchRequestResult,但您已经定义了您期望从 var documents 获得的类型。

在类似的示例中,我在代码中定义了一个对象数组,我省略了强制向下转换,然后 try 将尝试 return NSFetchRequestResult 作为已经ScanDocument 对象的定义数组。

所以这应该可行...

    documents = try managedContext.fetch(fetchRequest)

我还注意到您正在使用 SwiftUI List...

第一条评论

所以你可以试试这个...

List(documents, id: \.id) { item in
    ZStack {
        DocumentCell(document: item)
    }
    .onChange(of: item) { _ in
        loadDocuments()
    }
}

(注:未测试)

但更重要的是...

评论2

您有没有使用 @FetchRequest@SectionedFetchRequest 视图构建器的原因?这些都将极大地简化您的代码并让生活变得更加有趣。

例如...

@FetchRequest(entity: ScanDocument.entity(),
              sortDescriptors: [
                NSSortDescriptor(keyPath: \.your1stAttributeAsKeyPath, ascending: true),
                NSSortDescriptor(keyPath: \.your2ndAttributeAsKeyPath, ascending: true)
              ] // these are optional and can be replaced with []
) var documents: FetchedResults<ScanDocument>

List(documents, id: \.id) { item in
    ZStack {
        DocumentCell(document: item)
    }
}

并且因为 SwiftUI 中的所有核心数据实体默认都是 ObservedObjects 并且也符合 Identifiable 协议,您也可以在列表中省略 id 参数。

例如...

List(documents) { item in
    ZStack {
        DocumentCell(document: item)
    }
}

变化:

var document: ScanDocument

收件人:

@ObservedObject var document: ScanDocument