TLDR - my FRC appears to get out of sync with the table view it is linked to. How can I get it to sync?
My View Controller (VC) has a table view called holesTable
that is managed by a fetched results controller (FRC) called fetchedResultsController
.
When the user presses the save button on VC, it calls the IBAction
called SaveHoles
. This saves the data in the FRC's managed object context then tests that all data rules have been followed. Any errors are caught and a message is sent on to another VC which displays the error to the user after an unwind (not shown here).
I'm discovering that the FRC doesn't have the same contents as the on-screen holesTable
because the function lookForSIProblems
doesn't pick up any errors when it called. However, I am able to validate that the underlying database has received and stored the data that's on the screen.
Subsequent calls to the VC will show the saved data and subsequent presses of the Save button will find the error problems.
So, it appears that my FRC results are out of sync with what is shown in holesTable
at the time I do the validation.
Here's the salient code:
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Hole> = {
// Create Fetch Request
let fetchRequest: NSFetchRequest<Hole> = Hole.fetchRequest()
// Configure Fetch Request
self.teeColourString = self.scorecard?.value(forKey: "teeColour") as! String?
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == %@", "appearsOn.offeredAt.name", self.courseName!, "appearsOn.teeColour", self.teeColourString!)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "holeNumber", ascending: true)]
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataManager.mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
@IBAction func saveHoles(_ sender: Any) {
print("Prepare to save")
do {
try fetchedResultsController.managedObjectContext.save()
}
catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
lookForSIProblems(holes: fetchedResultsController.fetchedObjects!)
}
func lookForSIProblems(holes: [Hole]) {
// Takes a fetched result set of 18 records and looks for duplicate or missing SI values
// It should not be possible to have duplicate hole numbers, so the check is done to see if the SI number has already been seen before and, if so, for which other hole.
var SIexists: [Bool] = [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
var SIToCheck: Int
let maxHole = 18
var currentHole = 0
var proceed = true
while currentHole < maxHole && proceed {
SIToCheck = Int(holes[currentHole].strokeIndex)
if SIToCheck == 0 {
// This can only happen when the scorecard has not been completed
messagesToPassBack = ["Incomplete Scorecard", "Not all holes have been given a valid Stroke Index. Your changes have been saved but you cannot enter player scores until this error has been fixed."]
flagIncompleteParForCourse(errorCode: 0)
proceed = false
} else if !SIexists[SIToCheck-1] {
// No SI for this hole number has yet appeared, so set it and carry on
SIexists[SIToCheck-1] = true
currentHole += 1
} else {
// This SI has already been seen
messagesToPassBack = ["Duplicate Stroke Index", "Stroke Index \(SIToCheck) has been duplicated. Your changes have been saved but you cannot enter player scores until this error has been fixed."]
flagIncompleteParForCourse(errorCode: 1)
proceed = false
}
}
}
EDIT #1 -
In response to the comment from @AgRizzo, the full FRC delegate methods are shown below.
These appear to be working well and is a carbon copy of delegate code I've used elsewhere in my app.
extension ScorecardViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
holesTable.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
holesTable.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
holesTable.reloadRows(at: [indexPath!], with: .automatic)
case .insert:
holesTable.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
holesTable.deleteRows(at: [indexPath!], with: .automatic)
case .move:
holesTable.moveRow(at: indexPath! as IndexPath, to: newIndexPath! as IndexPath)
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
holesTable.insertSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
case .delete:
holesTable.deleteSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
case .move:
break
case .update:
break
}
}
}
EDIT #2:
To see if the FRC was updating on each edit, I added a call to lookForSIProblems
into the FRC delegate methods thus:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
holesTable.reloadRows(at: [indexPath!], with: .automatic)
lookForSIProblems(fetchedResultsController.fetchedObjects!)
case .insert:
holesTable.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
holesTable.deleteRows(at: [indexPath!], with: .automatic)
case .move:
holesTable.moveRow(at: indexPath! as IndexPath, to: newIndexPath! as IndexPath)
}
}
And the result is that the FRC is showing values that are in sync with the table. I went on to isolate where I think the problem is. If I edit a fetched object in the table then immediately hit the Save button, the FRC in lookForSIProblems
seems to be using 'dirty' data. However, if I edit the object in the table but click on another item in the table, say, then the FRC in lookForSIProblems
uses 'fresh' data.
Not sure what this means.