Frustrating fact: After calling tableView:moveRowAtIndexPath:toIndexPath:
, tableView:cellForRowAtIndexPath:
doesn\'t get called for that row.
I do not think it is possible to do a move and a reload at the same time. I've tried several approaches and the best solution I've come up with is to do the reload just before the batch updates. I don't animate reloadRows
because it seems to conflict with the batch update animations.
[tableView reloadRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationNone];
[tableView beginUpdates];
//inserts, deletes and moves here
[tableView endUpdates];
Also, I typically put my cell configuration logic in a separate method like:
- (void)tableView:(UITableView *)tableView configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
So that I can just call it directly and bypass reloadRowsAtIndexPaths
altogether. You won't get the built in animation this way either, but you can do your own animations.
[_tableview beginUpdates];
// write code here to delete and move row...
[_tableview endUpdates];
// now after end update call reload method to reload cell..
[self.tableview reloadRowsAtIndexPaths:[NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:_arrayForData.count-1 inSection:indexpathforSelectedRow.section], nil] withRowAnimation:UITableViewRowAnimationNone];
I've had success with [TableView reloadData]. Make sure you are updating your data source, and placing the reload code within a spot that is appropriate.
I ran into a similar problem when sorting on "dateModified" but I was also displaying that property on the cell's label. Both "move" and "update" were required. "move" was only called so the correct cell was brought to the top of the list, but the label text didn't update.
My solution for a simple UITableViewCell.
First, you make call .move as normal. Directly after you make a call to a custom configure method — which is responsible for animating the "update" on the cell.
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: [indexPath!], with: .fade)
case .update:
tableView.reloadRows(at: [indexPath!], with: .fade)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
// Can't "reload" and "move" to same cell simultaneously.
// This is an issue because I'm sorting on date modified and also displaying it within a
// label in the UITableViewCell.
// To have it look perfect you have to manually crossfade the label text, while the UITableView
// does the "move" animation.
let cell = tableView.cellForRow(at: indexPath!)!
let note = fetchedResultsController.object(at: newIndexPath!)
configure(cell: cell, note: note, animated: true)
}
}
The configure method looks like this (note animated is optional):
internal func configure(cell: UITableViewCell, note: Note, animated: Bool = false) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .medium
let dateString = dateFormatter.string(from: note.dateModified as Date)
if animated {
UIView.transition(with: cell.contentView, duration: 0.3, options: .transitionCrossDissolve, animations: {
cell.textLabel?.text = dateString
}, completion: nil)
} else {
cell.textLabel?.text = dateString
}
}
Reuse the configure method here without animation:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifier, for: indexPath)
let note = fetchedResultsController.object(at: indexPath)
configure(cell: cell, note: note)
return cell
}
If you have a more complicated cell (subclass), you could probably move the configure method to the subclass code. The important part is having a method to update the cell data with animation optional.