UITableView row animation duration and completion callback

45,961

Solution 1

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 2

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 3

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 4

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 5

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.

Share:
45,961

Related videos on Youtube

Daniel Dickison
Author by

Daniel Dickison

I love Lisp, finding Swift pretty great, and I'd probably get hooked on Rust if I found some time to get to know it. Also: I don't hate JavaScript.

Updated on June 03, 2021

Comments

  • Daniel Dickison
    Daniel Dickison almost 3 years

    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];
    
    • Kalle
      Kalle over 11 years
      I haven't tried myself, but maybe this could do it, with some index path handling: - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
  • JLundell
    JLundell over 11 years
    I have tried this (just now) and it seems to work just fine. Bonus: this implies that you could also use CATransaction to set the animation duration and timing function. Nice!
  • JLundell
    JLundell over 11 years
    See karwag's answer above. You need to solve the problem of what counts as "afterwards".
  • JLundell
    JLundell over 11 years
    On further experimentation, the behavior of this mechanism in this context seems erratic at best. Note also that the documentation says that it applies only to animations added after the call.
  • karwag
    karwag over 11 years
    Yes, you're right about that. Setting the completion block before the call to insertRowsAtIndexPaths is how it should be done (also added a CATransaction begin/commit to make sure). This is exactly how I've implemented it in my apps and it works flawlessly. Nice if you want to chain an animation off the insert/delete animation.
  • jimt
    jimt over 11 years
    FWIW, this does not work for me. The callback gets hit long, long after the actual animation is complete. (iOS 6)
  • karwag
    karwag over 11 years
    Again, works flawlessly here. iOS6 and all. This is a proper SDK-supported mechanism for overriding properties in default animations. Perhaps you have additional, longer-running animations inside your CATransaction? They get nested, you know.
  • Aron
    Aron over 11 years
    Works great for me in iOS6. Thanks for that!
  • Tom Redman
    Tom Redman over 11 years
    setAnimationDuration doesn't seem to affect the insert/delete duration. iOS 6
  • Gabriel
    Gabriel almost 11 years
    This is awesome; works great for me under both iOS 5.1 and 6.x. Was looking for a good way to trigger an action once a table view animation had completed and this fit the bill perfectly!
  • Jeff Grimes
    Jeff Grimes almost 11 years
    any suggestions for how to change the duration though? CATransaction setAnimationDuration: doesn't seem to make a difference.
  • slamor
    slamor over 10 years
    Works fine for me too in iOS 5.1.1, 6.1, 7.0; But, if you need to get a new tableView.contentSize after animation (as it was in my case), you must use [self performSelectorOnMainThread:withObject:waitUntilDone:]; in setCompletionBlock in order to call your delegate in next runloop. if you call your delegate directly, without performSelectorOnMainThread, you get old value for tableView.contentSize.
  • trickster77777
    trickster77777 about 10 years
    today you - my personal Super Hero!
  • Danny
    Danny almost 9 years
    When doing a row insert this way, or @Brent's way, although the duration is respected, the UITableViewRowAnimation does not seem to be respected and always appears to animate top down, even when I specify, for example UITableViewRowAnimationLeft. Testing on iOS 8.4 - anybody have a solution?
  • Marius Kažemėkaitis
    Marius Kažemėkaitis over 8 years
    Thanks, nice answer! Solves lots of issues.
  • mbo42
    mbo42 over 8 years
    Works great for iOS 9
  • primulaveris
    primulaveris over 8 years
    Beautiful! Here's a swift version in my answer below.
  • Prajeet Shrestha
    Prajeet Shrestha almost 8 years
    This seems to work without [tableView beginUpdates] and [tableView endUpdate] method call as well .
  • Dustin
    Dustin over 7 years
    The animation duration appears to be ignored.
  • Nicholas Smith
    Nicholas Smith over 7 years
    Perfect solution, would recommend to mods that this should be the accepted answer.
  • hetelek
    hetelek about 7 years
    Be careful to not set the completion block after performing your animations. If you set the completion block after, it will be executed immediately.
  • Gianni Carlo
    Gianni Carlo about 7 years
    Awesome answer! this is one of the reasons I love Swift
  • tryKuldeepTanwar
    tryKuldeepTanwar over 6 years
    can we use this method for reload row at index path?
  • Jonny
    Jonny about 6 years
    Not having used it myself yet, but in iOS 11 a new function performBatchUpdates should be preferred over beginUpdates: developer.apple.com/documentation/uikit/uitableview/…
  • CyberMew
    CyberMew about 6 years
    @GianniCarlo you can do this in ObjC as well
  • Gianni Carlo
    Gianni Carlo about 6 years
    @CyberMew yes, but creating a Category has always been such a pain, specially due to the long names of the extra files
  • kemdo
    kemdo almost 6 years
    it's only available in ios 11, how to use it in ios 10?
  • Frederic Adda
    Frederic Adda almost 6 years
    @kemdo Why do you say it's only available in iOS 11? Everything here is iOS 2+ except setCompletionBlock which is iOS 4+
  • kemdo
    kemdo almost 6 years
    I enter wrong performBatchUpdate sorry. Your solution is work as expect. THanks you
  • Daniel Dickison
    Daniel Dickison over 4 years
    This is great. I hadn't noticed this addition.
  • malhal
    malhal almost 4 years
    beginUpdates doesn't need to be after begin