How to detect that animation has ended on UITableView beginUpdates/endUpdates?
I am inserting/deleting tabl开发者_运维问答e cell using insertRowsAtIndexPaths/deleteRowsAtIndexPaths
wrapped in beginUpdates/endUpdates
. I am also using beginUpdates/endUpdates
when adjusting rowHeight. All these operations are animated by default.
How can I detect that animation has ended when using beginUpdates/endUpdates
?
What about this?
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// animation has finished
}];
[tableView beginUpdates];
// do some work
[tableView endUpdates];
[CATransaction commit];
This works because the tableView animations use CALayer
animations internally. That is, they add the animations to any open CATransaction
. If no open CATransaction
exists (the normal case), then one is implicitly began, which is ended at the end of the current runloop. But if you begin one yourself, like is done here, then it will use that one.
Swift Version
CATransaction.begin()
CATransaction.setCompletionBlock({
do.something()
})
tableView.beginUpdates()
tableView.endUpdates()
CATransaction.commit()
If you're targeting iOS 11 and above, you should use UITableView.performBatchUpdates(_:completion:)
instead:
tableView.performBatchUpdates({
// delete some cells
// insert some cells
}, completion: { finished in
// animation complete
})
A possible solution could be to inherit from the UITableView on which you call endUpdates
and overwrite its setContentSizeMethod
, since UITableView adjusts its content size to match the added or removed rows. This approach should also work for reloadData
.
To ensure that a notification is sent only after endUpdates
is called, one could also overwrite endUpdates
and set a flag there.
// somewhere in header
@private BOOL endUpdatesWasCalled_;
-------------------
// in implementation file
- (void)endUpdates {
[super endUpdates];
endUpdatesWasCalled_ = YES;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
if (endUpdatesWasCalled_) {
[self notifyEndUpdatesFinished];
endUpdatesWasCalled_ = NO;
}
}
You can enclose your operation(s) in UIView animation block like so:
- (void)tableView:(UITableView *)tableView performOperation:(void(^)())operation completion:(void(^)(BOOL finished))completion
{
[UIView animateWithDuration:0.0 animations:^{
[tableView beginUpdates];
if (operation)
operation();
[tableView endUpdates];
} completion:^(BOOL finished) {
if (completion)
completion(finished);
}];
}
Credits to https://stackoverflow.com/a/12905114/634940.
Haven't found a good solution yet (short of subclassing UITableView). I've decided to use performSelector:withObject:afterDelay:
for now. Not ideal, but gets the job done.
UPDATE: It looks like I can use scrollViewDidEndScrollingAnimation:
for this purpose (this is specific to my implementation, see comment).
You can use tableView:willDisplayCell:forRowAtIndexPath:
like:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"tableView willDisplay Cell");
cell.backgroundColor = [UIColor colorWithWhite:((indexPath.row % 2) ? 0.25 : 0) alpha:0.70];
}
But this will also get called when a cell that is already in the table moves from off the screen to on the screen so it may not be exactly what you are looking for. I just looked through all the UITableView
and UIScrollView
delegate methods and there doesnt appear to be anything to handle just after a cell is inserted animation.
Why not just call the method you want to be called when the animation ends after the endUpdates
?
- (void)setDownloadedImage:(NSMutableDictionary *)d {
NSIndexPath *indexPath = (NSIndexPath *)[d objectForKey:@"IndexPath"];
[indexPathDelayed addObject:indexPath];
if (!([table isDragging] || [table isDecelerating])) {
[table beginUpdates];
[table insertRowsAtIndexPaths:indexPathDelayed withRowAnimation:UITableViewRowAnimationFade];
[table endUpdates];
// --> Call Method Here <--
loadingView.hidden = YES;
[indexPathDelayed removeAllObjects];
}
}
精彩评论