UITableView row animation duration and completion callback

IosIphoneUitableviewAnimationCocoa Touch

Ios Problem Overview


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];

Ios Solutions


Solution 1 - Ios

Just came across this. Here's how to do it:

Objective-C

[CATransaction begin];
[tableView beginUpdates];
[CATransaction setCompletionBlock: ^{
    // Code to be executed upon completion
}];
[tableView insertRowsAtIndexPaths: indexPaths
                 withRowAnimation: UITableViewRowAnimationAutomatic];
[tableView endUpdates];
[CATransaction commit];

Swift

CATransaction.begin()
tableView.beginUpdates()
CATransaction.setCompletionBlock {
    // Code to be executed upon completion
}
tableView.insertRowsAtIndexPaths(indexArray, withRowAnimation: .Top)
tableView.endUpdates()
CATransaction.commit()

Solution 2 - Ios

Expanding on karwag's fine answer, note that on iOS 7, surrounding the CATransaction with a UIView Animation offers control of the table animation duration.

[UIView beginAnimations:@"myAnimationId" context:nil];

[UIView setAnimationDuration:10.0]; // Set duration here

[CATransaction begin];
[CATransaction setCompletionBlock:^{
    NSLog(@"Complete!");
}];

[myTable beginUpdates];
// my table changes
[myTable endUpdates];

[CATransaction commit];
[UIView commitAnimations];

The UIView animation's duration has no effect on iOS 6. Perhaps iOS 7 table animations are implemented differently, at the UIView level.

Solution 3 - Ios

That's one hell of a useful trick! I wrote a UITableView extension to avoid writing CATransaction stuff all the time.

import UIKit

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()
    }

}

This is used like so:

// Insert in the tableView the section we just added in sections
self.tableView.performUpdate({
    self.tableView.insertSections([newSectionIndex], with: UITableViewRowAnimation.top)

}, completion: {
    // Scroll to next section
    let nextSectionIndexPath = IndexPath(row: 0, section: newSectionIndex)
    self.tableView.scrollToRow(at: nextSectionIndexPath, at: .top, animated: true)
})

Solution 4 - Ios

Shortening Brent's fine answer, for at least iOS 7 you can wrap this all tersely in a [UIView animateWithDuration:delay:options:animations:completion:] call:

[UIView animateWithDuration:10 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
  [self.tableView beginUpdates];
  [self.tableView endUpdates];
} completion:^(BOOL finished) {
  // completion code
}];

though, I can't seem to override the default animation curve from anything other than EaseInOut.

Solution 5 - Ios

Here's a Swift version of karwag's answer

CATransaction.begin()
tableView.beginUpdates()
CATransaction.setCompletionBlock { () -> Void in
    // your code here
}
tableView.insertRowsAtIndexPaths(indexArray, withRowAnimation: .Top)
tableView.endUpdates()
CATransaction.commit()

Solution 6 - Ios

For me I needed this for a collectionView. I've made a simple extension to solve this:

extension UICollectionView {

    func reloadSections(sections: NSIndexSet, completion: () -> Void){
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        
        self.reloadSections(sections)
        
        CATransaction.commit()
    }
    
}

Solution 7 - Ios

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.

Solution 8 - Ios

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()
        }
    }
}

Solution 9 - Ios

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		
})

Solution 10 - Ios

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 :)

Solution 11 - Ios

You could try to wrap the insertRowsAtIndexPath in a

- (void)beginUpdates
- (void)endUpdates

transaction, then do the flash afterwards.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaniel DickisonView Question on Stackoverflow
Solution 1 - IoskarwagView Answer on Stackoverflow
Solution 2 - IosBrentView Answer on Stackoverflow
Solution 3 - IosFrédéric AddaView Answer on Stackoverflow
Solution 4 - IosvisnupView Answer on Stackoverflow
Solution 5 - IosprimulaverisView Answer on Stackoverflow
Solution 6 - IosAntoineView Answer on Stackoverflow
Solution 7 - IosMichał ZiobroView Answer on Stackoverflow
Solution 8 - IosStanislau BaranouskiView Answer on Stackoverflow
Solution 9 - IosadamjanschView Answer on Stackoverflow
Solution 10 - IosStanislav M.View Answer on Stackoverflow
Solution 11 - IosJordanView Answer on Stackoverflow