daspianist
daspianist

Reputation: 5495

UITableView performing row deletion and insertion in a cascading animation manner

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 IndexPaths 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

Answers (1)

paulvs
paulvs

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:

  1. After deleting a row, other indices (index paths and also indices into data sources) don't point to where they used to.
  2. The function 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

Related Questions