Search code examples
iosswiftcore-datansfetchedresultscontroller

Sort Sections in NSFetchedResultsController


I am using an NSFetchedResultsController to manage my tableView, the problem is when another section is added, the NSFetchedResultsController automatically puts it at index 0, this is causing several problems in my app. Below is how I set up my NSFetchedResultsController, I am fetching type CDSet, and each section is of type exercise

the relationship between the two is as follows CDSet <<---> Exercise

how can I sort the sections by exercise.exerciseIndex?

let fetchRequest:NSFetchRequest<CDSet> = CDSet.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "set_index", ascending: true)]
fetchRequest.predicate = NSPredicate(format: "exercise.workout == %@", currentWorkout)

fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DataController.shared.context, sectionNameKeyPath: "exercise.exerciseIndex", cacheName: nil)


fetchedResultsController.delegate = self

do {
    try fetchedResultsController.performFetch()
} catch {
    
}

update: I've updated the sortDescriptor to the following:

fetchRequest.sortDescriptors = [NSSortDescriptor(key: "exercise.exerciseIndex", ascending: true), NSSortDescriptor(key: "set_index", ascending: true)]

but am now getting this error when trying to add a second section:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (2 inserted, 0 deleted).'

Unsure why it is trying to insert 2 when I have only specified 1

Here are my NSFetchedResultsController delegate methods:

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }
    
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }
    
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
        
        let indexPath = IndexPath(row: 0, section: sectionIndex)
        let indexSet = IndexSet(indexPath)
        tableView.insertSections(indexSet, with: .fade)
    }
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        
        
        switch type {
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
            tableView.deleteRows(at: [newIndexPath!], with: .fade)
        default:
            break
        }
    }

Solution

  • You have to specify two sort descriptors.

    • The first sorts the sections, it must be the equivalent to the section key path.
    • The second sorts the items within the sections.

    Edit:

    The usual implementation of controller(_:didChange: atSectionIndex:for:) is

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
          switch type {
              case .insert: self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
              case .delete: self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
              default: return
          }
      }