9

The problem that I am having is that when I update a core data Asset object from a sheet view, the change is not reflected in the UI of the AssetListView. (Note that Inserting a new object from the sheet view does refresh the UI of the AssetListView. Deleting an object in sheet view also refreshes the UI of the AssetListView) The only action that is not working is the Update.

How can I make the AssetListView change when the core data object changes?

I have the following SwiftUI code showing a list of assets from a CoreData FetchRequest:

struct AssetListView: View {
    @State private var showingSheet = false
    @State private var selectedAssetId: NSManagedObjectID?
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Asset.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Asset.allocationPercentage, ascending: false)]) var assets: FetchedResults<Asset>
    
    var body: some View {
        VStack {
            Form {
                 ForEach(assets, id: \.self) { asset in
                    Section {
                        AssetRowView(asset: asset)
                            .onTapGesture {
                                self.selectedAssetId = asset.objectID
                                self.showingSheet = true
                        }
                    }
                }
            }
            
        }
        .navigationBarTitle("Assets").sheet(isPresented: $showingSheet ) {
                EditAssetView(assetId: self.selectedAssetId!)
                    .environment(\.managedObjectContext, self.moc)
                }
        }
     }
}

And this is an edit screen, which I present as SwiftUI sheet:

struct EditAssetView: View {
    var assetId: NSManagedObjectID
    @Environment(\.presentationMode) var presentationMode
    @State private var name = ""
    @State private var description = ""
    @Environment(\.managedObjectContext) var moc
    
    var asset: Asset {
        moc.object(with: assetId) as! Asset
    }
   
    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Name", text: $name)
                    TextField("Description", text: $description)
                }
            }
            .navigationBarTitle(Text("Edit Asset"), displayMode: .inline)
            .navigationBarItems(leading: Button("Cancel") {
                self.presentationMode.wrappedValue.dismiss()
                }, trailing: Button("Done") {
                    self.presentationMode.wrappedValue.dismiss()
                    self.asset.name = self.name
                    self.asset.assetDescription = self.description
                    try? self.moc.save()
                }
            )
        }
        .onAppear {
            self.name = self.asset.name
            self.description = self.asset.assetDescription
        }
    }
}

Here is the code for AssetRowView:

struct AssetRowView: View {
    var asset: Asset?
    
    var body: some View {
        HStack {
           Text(asset.name)
           Text(asset.assetDescription)
        }
    }
}
AAV
  • 373
  • 1
  • 5
  • 17

4 Answers4

27

Try to observe Asset (cause NSManagedObject is-a ObservableObject)

struct AssetRowView: View {
    @ObservedObject var asset: Asset        // << here !!
    
    var body: some View {
        HStack {
           Text(asset.name)
           Text(asset.assetDescription)
        }
    }
}

if that will not be enough, also might be needed to update Done button action as

}, trailing: Button("Done") {
    self.asset.objectWillChange.send()             // << here !!
    self.asset.name = self.name
    self.asset.assetDescription = self.description
    try? self.moc.save()

    // better do this at the end of activity
    self.presentationMode.wrappedValue.dismiss()
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 2
    thank you! Just making it ObservedObject worked. Did not need to call objectWillChange.send() – AAV Jul 09 '20 at 16:13
  • if UI element is from relationship, how to update it? for example, class <->> student, a class has some students. In class list view, every class cell shows class name and student count. when tap on class cell, can edit class and add/remove students. after students changed, how can i update class cell? – foolbear Apr 07 '21 at 09:10
3

What I did is pass the core data object as:

@ObservedObject var toDo: FetchedResults<ToDoModelCoreData>.Element

from my fetch result

@FetchRequest var todoFetchRequest: FetchedResults<ToDoModelCoreData>

and that will make sure swiftUI view is updated when coredata it is

zdravko zdravkin
  • 2,090
  • 19
  • 21
2

adding @ObservedObject to the core data object works, but you may need to scroll the list to see the change. depending on how you set up your predicate, managed objects, and fetch request.

adding foo.objectWillChange.send() at the end of whatever process edits the NSManagedObject fixes this.

spnkr
  • 952
  • 9
  • 18
0

In my case, I needed to trigger an update to an list view after an item in a sheet was edited. The solution was to call the objectWillChange.send() methods on the parent ManagedObject in the DidDismiss handler.

      .sheet(item: $selectedChildItem, onDismiss: editViewDidDismiss) { childItem in  
            EditItemDetailsView(item: childItem)
        }

   private func editViewDidDismiss(){
    self.parentObject.objectWillChange.send()
}