UITableView row animation duration and completion callback
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.
Related videos on Youtube
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, 2021Comments
-
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 over 11 yearsI 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 over 11 yearsI 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 over 11 yearsSee karwag's answer above. You need to solve the problem of what counts as "afterwards".
-
JLundell over 11 yearsOn 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 over 11 yearsYes, 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 over 11 yearsFWIW, this does not work for me. The callback gets hit long, long after the actual animation is complete. (iOS 6)
-
karwag over 11 yearsAgain, 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 over 11 yearsWorks great for me in iOS6. Thanks for that!
-
Tom Redman over 11 years
setAnimationDuration
doesn't seem to affect the insert/delete duration. iOS 6 -
Gabriel almost 11 yearsThis 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 almost 11 yearsany suggestions for how to change the duration though? CATransaction setAnimationDuration: doesn't seem to make a difference.
-
slamor over 10 yearsWorks 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 about 10 yearstoday you - my personal Super Hero!
-
Danny almost 9 yearsWhen 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 over 8 yearsThanks, nice answer! Solves lots of issues.
-
mbo42 over 8 yearsWorks great for iOS 9
-
primulaveris over 8 yearsBeautiful! Here's a swift version in my answer below.
-
Prajeet Shrestha almost 8 yearsThis seems to work without [tableView beginUpdates] and [tableView endUpdate] method call as well .
-
Dustin over 7 yearsThe animation duration appears to be ignored.
-
Nicholas Smith over 7 yearsPerfect solution, would recommend to mods that this should be the accepted answer.
-
hetelek about 7 yearsBe 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 about 7 yearsAwesome answer! this is one of the reasons I love Swift
-
tryKuldeepTanwar over 6 yearscan we use this method for reload row at index path?
-
Jonny about 6 yearsNot having used it myself yet, but in iOS 11 a new function
performBatchUpdates
should be preferred overbeginUpdates
: developer.apple.com/documentation/uikit/uitableview/… -
CyberMew about 6 years@GianniCarlo you can do this in ObjC as well
-
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 almost 6 yearsit's only available in ios 11, how to use it in ios 10?
-
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 almost 6 yearsI enter wrong performBatchUpdate sorry. Your solution is work as expect. THanks you
-
Daniel Dickison over 4 yearsThis is great. I hadn't noticed this addition.
-
malhal almost 4 yearsbeginUpdates doesn't need to be after begin