UITableView row animation duration and completion callback

Viewed 46461

Is there a way to either specify the duration for UITableView row animations, or to get a callback when the animation completes?

What I would like to do is flash the scroll indicators after the animation completes. Doing the flash before then doesn't do anything. So far the workaround I have is to delay half a second (that seems to be the default animation duration), i.e.:

[self.tableView insertRowsAtIndexPaths:newRows
                      withRowAnimation:UITableViewRowAnimationFade];
[self.tableView performSelector:@selector(flashScrollIndicators)
                     withObject:nil
                     afterDelay:0.5];
11 Answers

Nowadays if you want to do this there is new function starting from iOS 11:

- (void)performBatchUpdates:(void (^)(void))updates 
                 completion:(void (^)(BOOL finished))completion;

In updates closures you place the same code as in beginUpdates()/endUpdates section. And the completion is executed after all animations.

As tableView's performBatch method is available starting from iOS 11 only, you can use following extension:

extension UITableView {
func performUpdates(_ updates: @escaping () -> Void, completion: @escaping (Bool) -> Void) {
        if #available(iOS 11.0, *) {
            self.performBatchUpdates({
                updates()
            }, completion: completion)
        } else {
            CATransaction.begin()
            beginUpdates()
            CATransaction.setCompletionBlock {
                completion(true)
            }
            updates()
            endUpdates()
            CATransaction.commit()
        }
    }
}

Antoine's answer is pretty good – but is for UICollectionView. Here it is for UITableView:

extension UITableView {
    func reloadSections(_ sections: IndexSet, with rowAnimation: RowAnimation, completion: (() -> Void)?) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        
        self.reloadSections(sections, with: rowAnimation)
        
        CATransaction.commit()
    }
}

Called like so:

tableView.reloadSections(IndexSet(0), with: .none, completion: {
    // Do the end of animation thing        
})

If someone is facing the problem when tableView is ignoring animation parameters from UIView.animate and using "from up to down" default animation for reloading rows, I've found a strange solution:

You need to:

  1. Silence tableView animation
  2. Use transitionAnimation instead

Example:

let indicesToUpdate = [IndexPath(row: 1, section: 0)]
UIView.transition(with: self.tableView,
                      duration: 0.5,
                      options: [.transitionCrossDissolve,
                                .allowUserInteraction,
                                .beginFromCurrentState],
                      animations: {
                        UIView.performWithoutAnimation {
                            self.tableView.reloadRows(at: indicesToUpdate,
                                                      with: .none)
                        }
                      })

PS: UIView.transition(..) also has optional completion :)

Related