UI with FetchRequest dosn’t refresh

Hello Everyone,


A short note first, unfortunately the following code is working, I am posting in anyway to show how the whole thing is built up. To make it executable, it still needs a CoreData table named "Person" with three columns (Id = UID, firstname = string, lastname = string).


The problem would be here, in the structure "PersonView", not when calling but when changing the detail view.

Here the changed record is simply removed from the view instead of displaying it. As long as there is no update, there is no problem.


I suppose that the problem in the original is that the @FetchRequest in PersonView is not refreshed. It could also be that the @FetchRequest uses a different managedObjectContext than the one saved.


Here are three questions:

a) How can you specify an @FetchRequest (see PersonView) which managedObjectContext it should use?

b) how could you restart the @FetchRequest when returning a detail view in PersonView

c) how could the functions load() and save() in Object DPerson be made nicer so that it doesn't look so bumpy.


As I said, the example unfortunately works, so please see it only as an illustration.


Many thanks in advance.


import SwiftUI
import CoreData


class DPerson: ObservableObject {
 @Published var vorname: String = ""
 @Published var nachname: String = ""
 var id: UUID?
 var ctx: NSManagedObjectContext?
 func setContext(ctx: NSManagedObjectContext){
  self.ctx = ctx
 }
 func load(id: UUID){
  let request: NSFetchRequest<Person> = Person.fetchRequest()
  request.predicate = NSPredicate(format: "id = %@", id.description)
  if let m = try? ctx?.fetch(request), m.count == 1 {
   self.id = m[0].id
   self.vorname = m[0].vorname ?? ""
   self.nachname = m[0].nachname ?? ""
  }
 }
 func save() {
  let request: NSFetchRequest<Person> = Person.fetchRequest()
  request.predicate = NSPredicate(format: "id = %@", id!.description)
  if let m = try? ctx?.fetch(request), m.count == 1 {
   m[0].vorname = self.vorname
   m[0].nachname = self.nachname
   try? ctx?.save()
  }
 }
}


struct PersonDetail: View {
 @Environment(\.managedObjectContext) var context
 let personId: UUID?
 @ObservedObject var dPerson: DPerson = DPerson()
 var body: some View {
  List{
   TextField("Vorname", text: $dPerson.vorname)
   TextField("Nachname", text: $dPerson.nachname)
  }.onAppear(perform: {
     self.dPerson.setContext(ctx: self.context)
     self.dPerson.load(id: self.personId!)
    })
   .onDisappear(perform: {
     self.dPerson.save()
    })
 }
}


struct ContentView: View {
 @Environment(\.managedObjectContext) var context
 struct PersonView: View {
  var fetchRequest: FetchRequest<Person>
  var body: some View {
   List(fetchRequest.wrappedValue, id: \.self) { person in
    NavigationLink(destination: PersonDetail(personId: person.id) ){
     Text("\(person.vorname ?? "") \(person.nachname ?? "")")
    }
   }
  }
  init() {
   fetchRequest = FetchRequest<Person>(entity: Person.entity(), sortDescriptors: [])
  }
 }

  var body: some View {
   VStack{
    NavigationView {
     PersonView()
    }
    Spacer()
     Button(action: {
      let a = Person(context: self.context)
      a.id = UUID()
      a.vorname = "Andy"
      a.nachname = "Zacherl"
      
      let b = Person(context: self.context)
      b.id = UUID()
      b.vorname = "Caro"
      b.nachname = "Smith"
      
      let c = Person(context: self.context)
      c.id = UUID()
      c.vorname = "Xaver"
      c.nachname = "Muller"
      
      try? self.context.save()
        
      }) {
        Text("Add DB")
    }
   }
  }
}

@FetchRequest always uses the managed object context from the environment; in general, you should always do the same.


When I've used SwiftUI fetch requests I've always used the property wrapper syntax, and it's worked happily enough. For example, I have a simple to-do list view:


struct TodoList: View {
    @FetchRequest<TodoItem> private var items: FetchedResults<TodoItem>

    init(request: NSFetchRequest<TodoItem>) {
        self._items = FetchRequest(fetchRequest: request)
    }

    var body: some View {
        List {
            ForEach(items, id: \.objectID) { item in 
                ItemRow(item: item)
            }
        }
    }
}


The ItemRow then uses an @ObservedObject to see the item, and reads the managed object context from the environment:


struct ItemRow: View {
    @ObservedObject var item: TodoItem
    @Environment(\.managedObjectContext) private var managedObjectContext
}


So far, this approach has worked for me, including doing things like editing items directly, adding and removing them. I'd recommend trying to diagnose your issue by taking a step back and attempting to build the UI without too much extra code around the model; see how far @FetchRequest and @ObservedObject alone can get you before writing your own code to load and save individual items. That way you'll have a better chance of locating the underlying issue.

UI with FetchRequest dosn’t refresh
 
 
Q