I've been trying to follow this tutorial for rearranging rows in a table view while using NSFetchedResultsController and NSFetchedResultsController delegate, but my project is extremely buggy. What happens is that when I try rearranging a row and drop it, the rows are randomised (probably not randomised, but I can't see the pattern to find the problem). What's wrong with my implementation?
Table view data source:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("RoutineCell", forIndexPath: indexPath) as! UITableViewCell
var routine = fetchedResultsController.objectAtIndexPath(indexPath) as! Routine
lastIndex++
// Configure the cell...
cell.textLabel?.text = routine.name
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
var routine = fetchedResultsController.objectAtIndexPath(indexPath) as! Routine
// Delete the row from the data source
managedContext.deleteObject(routine)
managedContext.save(nil)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
if var routines = fetchedResultsController.fetchedObjects {
let routine = routines[sourceIndexPath.row] as! Routine
println("Instance of routine: \(routines[sourceIndexPath.row].description) created")
routines.removeAtIndex(sourceIndexPath.row)
println("Fetched data is: \(routines.description)")
println("Routine removed at \(routines[sourceIndexPath.row])")
routines.insert(routine, atIndex: destinationIndexPath.row)
println("Routine inserted at index \(destinationIndexPath.row)")
var idx: Int = Int(routines.count)
for routine in routines as! [Routine] {
routine.index = idx--
}
managedContext.save(nil)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
tableView.reloadRowsAtIndexPaths(tableView.indexPathsForVisibleRows()!, withRowAnimation: UITableViewRowAnimation.Fade)
})
}
NSFetchedResultsControllerDelegate methods:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
}
func controller(
controller: NSFetchedResultsController,
didChangeObject anObject: AnyObject,
atIndexPath indexPath: NSIndexPath?,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: NSIndexPath?) {
// implementation to follow...
switch type {
case .Insert:
// Note that for Insert, we insert a row at the __newIndexPath__
if let insertIndexPath = newIndexPath {
self.tableView.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: .Fade)
}
case .Delete:
// Note that for Delete, we delete the row at __indexPath__
if let deleteIndexPath = indexPath {
self.tableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: .Fade)
}
case .Update:
// Note that for Update, we update the row at __indexPath__
if let updateIndexPath = indexPath {
let cell = self.tableView.cellForRowAtIndexPath(updateIndexPath)
let routine = self.fetchedResultsController.objectAtIndexPath(updateIndexPath) as? Routine
cell?.textLabel?.text = routine?.name
}
case .Move:
// Note that for Move, we delete the row at __indexPath__
if let deleteIndexPath = indexPath {
self.tableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: .Fade)
}
// Note that for Move, we insert a row at the __newIndexPath__
if let insertIndexPath = newIndexPath {
self.tableView.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: .Fade)
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
I did the same moves as in the gif above:
Your routine updating the data model is a bit difficult to read. You are assigning the highest
index
to the record on top of the list counting down to1
and presumably reverse sorting... OK, let's assume that is working. *)Perhaps you should simply disable the delegate methods when you process user generated changes of the data? Bracket your operations including the save with
*) I think that if you were initially counting up and then reorder counting down with the fetched results controller counting up you would get the kind of errors illustrated in your image.