Reputation: 5495
I am interested in deleting a batch of rows from a UITableView
using the animation tableView.deleteRows(at: [singleIndexPath], with: .automatic)
. However, my goal is to cascade the deletion animation so that they happen in a cascading animated manner, so that rather than the entire batch of rows being removed, the animnation will show them removed one after another.
I discovered a helpful UITableView
extension where I get a completion block after an animation is finished, as such:
extension UITableView {
/// Perform a series of method calls that insert, delete, or select rows and sections of the table view.
/// This is equivalent to a beginUpdates() / endUpdates() sequence,
/// with a completion closure when the animation is finished.
/// Parameter update: the update operation to perform on the tableView.
/// Parameter completion: the completion closure to be executed when the animation is completed.
func performUpdate(_ update: ()->Void, completion: (()->Void)?) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
// Table View update on row / section
beginUpdates()
update()
endUpdates()
CATransaction.commit()
}
}
My problem is figuring out how to utilize the block automate the animation of the row deletion (and later, row insertion). Right now, the following example code with two row IndexPath
s still has animation occurring at the same time, which is not my goal.
let singleIndexPath = IndexPath(row: 0, section: 0)
let anotherIndexPath = IndexPath(row: 1, section: 0)
self.tableView.performUpdate({
self.tableView.deleteRows(at: [singleIndexPath, anotherIndexPath], with: .automatic)
self.tableView.insertRows(at: [singleIndexPath, anotherIndexPath], with: .left)
//Happening at the same time, which is not what I want
}, completion: {
})
Any tip is much appreciated.
Upvotes: 3
Views: 507
Reputation: 12053
Cascade Table View Row Deletion
Usage:
let indexPathsToDelete = [IndexPath(row: 1, section: 0), IndexPath(row: 4, section: 0), IndexPath(row: 6, section: 0)]
deleteRowsAt(indexPaths: indexPathsToDelete, in: tableView)
Code:
func deleteRowsAt(indexPaths: [IndexPath], in tableView: UITableView) {
var state = (0 ..< tableView.numberOfSections).map {
Array(0 ..< tableView.numberOfRows(inSection: $0))
}
func cascadeDeleteAt(indexPath indexPathToDelete: IndexPath) {
// Get the current row for the index path we wish to delete.
// It could be diffrent to its original index path if other rows have been deleted.
guard let currentRow = state[indexPathToDelete.section].index(of: indexPathToDelete.row) else { return }
let currentIndexPath = IndexPath(row: currentRow, section: indexPathToDelete.section)
print(currentIndexPath)
// --- IMPLEMENT THE DELETION FROM YOUR DATA SOURCE ---
source.remove(at: currentIndexPath.row)
state[indexPathToDelete.section].remove(at: currentIndexPath.row)
tableView.performUpdate({
tableView.deleteRows(at: [currentIndexPath], with: .left)
}, completion: {
guard var idx = indexPaths.index(of: indexPathToDelete) else { return }
idx += 1
if idx < indexPaths.count {
let nextIndexPathToDelete = indexPaths[idx]
cascadeDeleteAt(indexPath: nextIndexPathToDelete)
}
})
}
if let indexPath = indexPaths.first {
cascadeDeleteAt(indexPath: indexPath)
}
}
When deleting rows in a cascade way (i.e. deleting one row at a time instead of all rows at once), the tricky parts are that:
performUpdate
needs to be called from within a recursive function to allow for the next row to be deleted only after the current row has deleted.Upvotes: 1