Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 906
rv7284 Avatar asked Jul 26 '20 13:07

rv7284


People also ask

How to implement core data in SwiftUI with contentview?

But make sure to use SwiftUI as the “Interface” mode and SwiftUI App as the “Life Cycle” mode. Also, make sure that you check the “Use Core Data” box. This will automatically set up the initial Core Data implementation for our app! Make sure you “reset” the ContentView by removing the generated code from it since we won’t need it for our app.

What is the best way to name a SwiftUI project?

You can name your project however you want, for instance, “PizzaRestaurant”. But make sure to use SwiftUI as the “Interface” mode and SwiftUI App as the “Life Cycle” mode. Also, make sure that you check the “Use Core Data” box.

How to create an app with SwiftUI in Xcode 12?

To get started, open Xcode 12 and create a new “App” under “Multiplatform” or “iOS”. You can name your project however you want, for instance, “PizzaRestaurant”. But make sure to use SwiftUI as the “Interface” mode and SwiftUI App as the “Life Cycle” mode. Also, make sure that you check the “Use Core Data” box.

How do I drive a SwiftUI view using a fetch request?

The simplest way to fetch data using a fetch request while responding to any changes that impact your fetch request's results is to use an NSFetchResultsController. While this object is commonly used in conjunction with table views and collection views, we can also use it to drive a SwiftUI view. Let's look at some code:


Video Answer


1 Answers

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
 
   ...
}
like image 179
Asperi Avatar answered Nov 14 '22 22:11

Asperi