【问题标题】:SwiftUI List not updating when core data property is updated in other view在其他视图中更新核心数据属性时,SwiftUI 列表不更新
【发布时间】:2020-11-15 22:34:36
【问题描述】:
@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({$0.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()

在重命名文档视图中:

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({$0.name}))

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

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

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

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

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

【问题讨论】:

    标签: ios core-data swiftui swiftui-list


    【解决方案1】:

    在尝试提供此问题的解决方案时,另一个考虑因素与类型定义有关,并且您将 fetch 请求结果强制转换为 ScanDocument 对象数组(即 [ScanDocument])。

    你的代码行...

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

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

    事实上,NSFetchRequest 本身会返回一个NSFetchRequestResult,但您已经定义了您期望从var documents 获得的类型。

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

    所以这应该工作......

        documents = try managedContext.fetch(fetchRequest)
    

    另外我注意到你正在使用 SwiftUI List...

    第 1 条评论

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

    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)
        }
    }
    

    【讨论】:

      【解决方案2】:

      Core Data 批量更新不会更新内存中的对象。之后您必须手动刷新。

      批处理操作绕过正常的 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])
      

      【讨论】:

        【解决方案3】:

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

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

        1. 添加新状态
        @State var documents: [ScanDocument] = []
        @State private var refreshID = UUID()   // can be actually anything, but unique
        
        1. 制作由它识别的列表
        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
         
           ...
        }
        

        【讨论】:

          猜你喜欢
          • 2020-06-30
          • 2020-10-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-19
          相关资源
          最近更新 更多