Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI update view on core data object change

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)
        }
    }
}
like image 262
AAV Avatar asked Jul 08 '20 21:07

AAV


3 Answers

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()
}
like image 187
Asperi Avatar answered Oct 11 '22 11:10

Asperi


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

like image 3
zdravko zdravkin Avatar answered Oct 11 '22 10:10

zdravko zdravkin


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.

like image 2
spnkr Avatar answered Oct 11 '22 10:10

spnkr