How to detect that animation has ended on UITableView beginUpdates/endUpdates?

IphoneObjective CIosIpad

Iphone Problem Overview


I am inserting/deleting table 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?

Iphone Solutions


Solution 1 - Iphone

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.

Solution 2 - Iphone

Swift Version


CATransaction.begin()

CATransaction.setCompletionBlock({
    do.something()
})

tableView.beginUpdates()
tableView.endUpdates()
                    
CATransaction.commit()

Solution 3 - Iphone

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

Solution 4 - Iphone

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

Solution 5 - Iphone

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.

Solution 6 - Iphone

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

Solution 7 - Iphone

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];
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpixelfreakView Question on Stackoverflow
Solution 1 - IphoneRudolf AdamkovičView Answer on Stackoverflow
Solution 2 - IphoneMichaelView Answer on Stackoverflow
Solution 3 - IphoneRobertView Answer on Stackoverflow
Solution 4 - IphoneAntoniView Answer on Stackoverflow
Solution 5 - IphoneZdenekView Answer on Stackoverflow
Solution 6 - IphonepixelfreakView Answer on Stackoverflow
Solution 7 - IphonechownView Answer on Stackoverflow