2

I have written an application displaying a list of students. I use a List of NavigationLink for that purpose. The students are ordered by one of their properties questionAskedClass which is an integer. All this information is stored within CoreData.

@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(entity: Student.entity(),
    sortDescriptors: [NSSortDescriptor(keyPath: \Student.questionAskedClass,
                                       ascending: true)])
var students: FetchedResults<Student>

var body: some View {
  NavigationView {
    List {
      ForEach(students) {student in
        NavigationLink(destination: StudentView(student: student)) {
          HStack {
            VStack(alignment: .leading) {
              Text("\(student.firstName)").font(.headline).foregroundColor(Color.black)
              Text("\(student.lastName)").font(.headline).foregroundColor(Color.gray)
            }
          }
        }
      }
    }
  }
}

When I press the name of the students, I switch to a new view called StudentView where I can get more information about the student and where I can update the property questionAskedClass

struct StudentView: View {
  @Environment(\.managedObjectContext) var managedObjectContext


  func askQuestion() {
    self.managedObjectContext.performAndWait {
      self.student.questionAskedClass += 1
      try? self.managedObjectContext.save()
    }
  }
}

Unfortunately, when I change that property, the ordering of the initial list is changed and I am taken away from the StudentView. The framework seems to get the feeling that the list needs to be reordered. But I just want this list to be reordered when I go back to the list. Not immediately when I change the value of questionAskedClass.

What can I do to mitigate this problem?

Thanks for your help.

InsideLoop
  • 6,063
  • 2
  • 28
  • 55

1 Answers1

0

You can try creating a simple NSFetchRequest<Student> and use the result of this fetch to update your students list.

@State var students: [Student] = []

fun refresh() {
    let fetchRequest = NSFetchRequest<Student>(entityName: "Student")
    students = try? managedObjectContext.fetch(fetchRequest) ?? []
}

You can trigger this refresh in onAppear so the list will be updated every time the View appears:

NavigationView {
    ...
}.onAppear {
    self.refresh()
}

Alternatively you can save your context in onAppear of the main view instead of StudentView:

struct StudentView: View {
    func askQuestion() {
        self.student.questionAskedClass += 1
    }
}

NavigationView {
    ...
}.onAppear {
    try? self.managedObjectContext.save()
}

If you want your data to persist when the app is terminated, add this function in AppDelegate (assuming your persistentContainer is declared there:

func applicationWillTerminate(_ application: UIApplication) {
    try? persistentContainer.viewContext.save()
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • Sorry. I am very new to Apple/Swift. Should I declare `var workingContext` within `StudentView`? Should I save both `workingContext` and `managedObjectContext` in the `askQuestion` function? – InsideLoop May 31 '20 at 22:11
  • I've now realised you'd like to do everything in `View` only. I've updated my answer. Usually people have problems the `View` it's not refreshing: [link](https://stackoverflow.com/questions/58643094/how-to-update-fetchrequest-when-a-related-entity-changes-in-swiftui). – pawello2222 May 31 '20 at 23:10
  • Thanks for your help. I have replaced the FetchRequest by `@FetchRequest(entity: Student.entity(), sortDescriptors: [])` and the ForEach by `ForEach(students.sorted { $0.questionAskedClass < $1.questionAskedClass })` (I guess this is what you meant), but the FetchRequest is still fired within `StudentView`. – InsideLoop Jun 01 '20 at 09:12
  • So far, the best I got is to create a local variable in `StudentView` that stores `questionAskedClass` from CoreData when the view appears and saves it back when the view disappears. But it is not saved back if we quit the application within `StudentView`. – InsideLoop Jun 01 '20 at 09:29
  • I think the whole point of using `@FetchRequest` is that your `students` variable stays up to date with what's in core data. When you change some `student` it forces to redraw your Student list view. To bypass auto-refreshing you can try creating a simple fetch request `NSFetchRequest` and fire it in `onAppear`. – pawello2222 Jun 01 '20 at 09:45