random random - 2 months ago 20
Swift Question

Updating sections with NSFetchedResultsController

I have an entity named

ToDoItem
. Which has three properties:

@NSManaged var toDoString: String?
@NSManaged var creationDate: NSDate?
@NSManaged var isComplete: NSNumber?


I'm trying to create a
NSFetchedResultsController
that will display with two section depending on the
isComplete
variable. I'm able to do that successfully buy doing this:

lazy var fetchedResultsController: NSFetchedResultsController = {

let questionAnswerFetchRequest = NSFetchRequest(entityName: "ToDoItem")

let questionAnswerFetchSortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
questionAnswerFetchRequest.sortDescriptors = [questionAnswerFetchSortDescriptor]

let frc = NSFetchedResultsController(
fetchRequest: questionAnswerFetchRequest,
managedObjectContext: (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext,
sectionNameKeyPath:"isComplete",
cacheName: nil)

frc.delegate = self
return frc
}()


I've also implemented the delete methods like this:

//
// MARK: Fetched Results Controller Delegate Methods
//
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}

func controller(controller: NSFetchedResultsController,
didChange sectionInfo: NSFetchedResultsSectionInfo,
atSectionIndex sectionIndex: Int,
for type: NSFetchedResultsChangeType) {

switch (type) {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
break
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
break
default:
break
}

}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch (type) {
case .Insert:
if let indexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
break;
case .Move:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}

if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
break;
}
}


The problem is that when I update the
isComplete
property I get this error:


CoreData: error: Serious application error. An exception was caught
from the delegate of NSFetchedResultsController during a call to
-controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after
the update (1) must be equal to the number of sections contained in
the table view before the update (0), plus or minus the number of
sections inserted or deleted (0 inserted, 0 deleted). with userInfo
(null)


Which I understand what this is saying, I just don't understand why it causing that error. I've implemented the delegate methods per the documentation.....




I've updated my
NSFetchedResultsController
creation to add another sortDescriptor per @wain's answer like so:

lazy var fetchedResultsController: NSFetchedResultsController = {

let questionAnswerFetchRequest = NSFetchRequest(entityName: "ToDoItem")

let isCompleteSortDescriptor = NSSortDescriptor(key: "isComplete", ascending: false)
let creationDateSortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)

questionAnswerFetchRequest.sortDescriptors = [isCompleteSortDescriptor, creationDateSortDescriptor]

let frc = NSFetchedResultsController(
fetchRequest: questionAnswerFetchRequest,
managedObjectContext: (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext,
sectionNameKeyPath:"isComplete",
cacheName: nil)

frc.delegate = self
return frc
}()


I've added some breakpoints and it turns out that this delegate method is never being called:

func controller(controller: NSFetchedResultsController,
didChange sectionInfo: NSFetchedResultsSectionInfo,
atSectionIndex sectionIndex: Int,
for type: NSFetchedResultsChangeType) {

switch (type) {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
break
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
break
default:
break
}
}


Which is leading to the same error as posted above. Any ideas on why that would never be called??

Answer

You need to add a sort descriptor for isComplete. It should be the first sort descriptor, then have your date one.

Basically, the sort and the sections need to be compatible, and yours currently aren't.