查看行动画持续时间和完成回调

是否有方法指定 UITableView 行动画的持续时间,或者在动画完成时获得回调?

我想做的是闪光的动画完成后滚动指示器。在那之前用闪光灯是没用的。到目前为止,我的解决方案是延迟半秒(这似乎是默认的动画持续时间) ,例如:

[self.tableView insertRowsAtIndexPaths:newRows
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView performSelector:@selector(flashScrollIndicators)
withObject:nil
afterDelay:0.5];
46730 次浏览

You could try to wrap the insertRowsAtIndexPath in a

- (void)beginUpdates
- (void)endUpdates

transaction, then do the flash afterwards.

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.

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.

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


}

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

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

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