How to update @FetchRequest, when a related Entity changes in SwiftUI?
You need a Publisher which would generate event about changes in context and some state variable in primary view to force view rebuild on receive event from that publisher.
Important: state variable must be used in view builder code, otherwise rendering engine would not know that something changed.
Here is simple modification of affected part of your code, that gives behaviour that you need.
@State private var refreshing = false
private var didSave = NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave)
var body: some View {
List {
ForEach(fetchedResults) { primary in
NavigationLink(destination: SecondaryView(primary: primary)) {
VStack(alignment: .leading) {
// below use of .refreshing is just as demo,
// it can be use for anything
Text("\(primary.primaryName ?? "nil")" + (self.refreshing ? "" : ""))
Text("\(primary.secondary?.secondaryName ?? "nil")").font(.footnote).foregroundColor(.secondary)
}
}
// here is the listener for published context event
.onReceive(self.didSave) { _ in
self.refreshing.toggle()
}
}
}
.navigationBarTitle("Primary List")
.navigationBarItems(trailing:
Button(action: {self.addNewPrimary()} ) {
Image(systemName: "plus")
}
)
}
I also struggled with this and found a very nice and clean solution:
You have to wrap the row in a separate view and use @ObservedObject in that row view on the entity.
Here's my code:
WineList:
struct WineList: View {
@FetchRequest(entity: Wine.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \Wine.name, ascending: true)
]
) var wines: FetchedResults<Wine>
var body: some View {
List(wines, id: \.id) { wine in
NavigationLink(destination: WineDetail(wine: wine)) {
WineRow(wine: wine)
}
}
.navigationBarTitle("Wines")
}
}
WineRow:
struct WineRow: View {
@ObservedObject var wine: Wine // !! @ObserveObject is the key!!!
var body: some View {
HStack {
Text(wine.name ?? "")
Spacer()
}
}
}
An alternative method: using a Publisher and List.id():
struct ContentView: View {
/*
@FetchRequest...
*/
private var didSave = NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave) //the publisher
@State private var refreshID = UUID()
var body: some View {
List {
...
}
.id(refreshID)
.onReceive(self.didSave) { _ in //the listener
self.refreshID = UUID()
print("generated a new UUID")
}
}
}
Every time you call save() of NSManagedObjects in a context, it genertates a new UUID for the List view, and it forces the List view to refresh.