UITableView row animation duration and completion callback
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];
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()
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.
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)
})
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.
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()
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()
}
}
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:
- Silence tableView animation
- 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 :)
You could try to wrap the insertRowsAtIndexPath in a
- (void)beginUpdates
- (void)endUpdates
transaction, then do the flash afterwards.
精彩评论