Frustrating fact: After calling tableView:moveRowAtIndexPath:toIndexPath:
, tableView:cellForRowAtIndexPath:
doesn't get called for that row.
Call reloadRowsAtIndexPaths:withRowAnimation:
after or before tableView:moveRowAtIndexPath:toIndexPath:
within UITableView
updates block also doesn't work: it raises Inconsistency error.
I see 2 workarounds: delete+insert instead of move or do reload within another updates block.
My question is: is there some other way to reload and move UITableView's row within same updates block?
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.
[_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 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With