ioscore-dataswiftuiswiftui-list

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({$0.name}))
    } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
    }
}

In the first view:

.onAppear(){
     self.loadDocuments()
 }

Now I'm pushing to detail view one single object:

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

In RenameDocumentView:

var document: ScanDocument

Also, one function to update the document name:

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

All this code works. This print statement always prints updated value:

print(documents.compactMap({$0.name}))

Here's the list code in main View:

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

But where user comes back to previous screen. The list shows old data. If I restart the app it shows new data.

Any help of nudge in a new direction would help.

There is a similar question here: SwiftUI List View not updating after Core Data entity updated in another View, but it's without answers.


Solution

  • NSManagedObject is a reference type so when you change its properties your documents is not changed, so state does not refresh view.

    Here is a possible approach to force-refresh List when you comes back

    1. add new state
    @State var documents: [ScanDocument] = []
    @State private var refreshID = UUID()   // can be actually anything, but unique
    
    1. make List identified by it
    List(documents, id: \.id) { item in
         ZStack {
              DocumentCell(document: item)
         }
    }.id(refreshID)     // << here
    
    1. change refreshID when come back so forcing List rebuild
    NavigationLink(destination: RenameDocumentView(document: documents[selectedDocumentIndex!])
                                   .onDisappear(perform: {self.refreshID = UUID()}), 
                    isActive: $pushActive) {
                        Text("")
                    }.hidden()
    

    Alternate: Possible alternate is to make DocumentCell observe document, but code is not provided so it is not clear what's inside. Anyway you can try

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