Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I reorder tableview rows using fetchedresultscontroller in swift?

I'm, trying to reorder the cells in a table view using core data and nsfetchresultscontroller. I found a few ways of doing this using objective C (of which I know nothing) and tried implementing them in swift, here's what I came up with:

var books: Array<AnyObject> = []

// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

    books = fetchedResultsController.fetchedObjects!

    let book = fetchedResultsController.objectAtIndexPath(fromIndexPath)

    self.fetchedResultsController.delegate = nil

    books.removeAtIndex(fromIndexPath.row)
    books.insert(book, atIndex: toIndexPath.row)

    var i = 0
    for book in books {
        book.setValue(i++, forKey: "position")
    }

    try! context.save()

    self.fetchedResultsController.delegate = self

}

The delegate and context references are in an extension in another file. With this code sometimes it works and sometimes it doesn't. I change the order, run the app again and it's in a completely different order that I can't figure out. Especially when I move more than one row at once. And if I print each object's "position" atribute to the console I can see they're really not being updated properly. But why not?

What am I doing wrong? What would be a better option?

Thanks in advance,

Daniel


Edit:

Ok, here's the working code:

func initializeFetchedResultsController() {
    let request = NSFetchRequest(entityName: "Book")
    let sortDescriptor = NSSortDescriptor(key: "position", ascending: true)
    request.sortDescriptors = [sortDescriptor]

    self.fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
    self.fetchedResultsController.delegate = self

    do {
        try self.fetchedResultsController.performFetch()
    } catch {
        fatalError("Failed to initialize FetchedResultsController: \(error)")
    }
}

// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

    initializeFetchedResultsController()
    var objects = self.fetchedResultsController.fetchedObjects! as! [ObjectClass]

    self.fetchedResultsController.delegate = nil

    let object = objects[fromIndexPath.row]
    objects.removeAtIndex(fromIndexPath.row)
    objects.insert(object, atIndex: toIndexPath.row)

    var i = 0
    for object in objects {
        object.position = i++
    }

    try! context.save()

    self.fetchedResultsController.delegate = self
}

The trick is that the initializeFetchedResultsController function, besides being at viewDidLoad, where I had it, must be repeated inside the moveRowAtIndexPath bit. Also, the initializing function and the array declaration must both be before setting the FRC delegate to nil (obviously, but it's easy to miss).

like image 387
dbmrq Avatar asked Oct 07 '15 09:10

dbmrq


1 Answers

You shouldn't edit books, it doesn't belong to you and is effectively private data. Instead you should just be updating the objects that are moving to change the position value. If you do this using array reordering as in your current logic you should create a new mutable copy of the array provided by the FRC. You should also be setting the new values for the position as NSNumber instances.

like image 108
Wain Avatar answered Oct 06 '22 00:10

Wain