Comment détecter que l'animation est terminée sur UITableView beginUpdates / endUpdates?

116

J'insère / supprime une cellule de tableau en utilisant insertRowsAtIndexPaths/deleteRowsAtIndexPathswrapped in beginUpdates/endUpdates. J'utilise également beginUpdates/endUpdateslors du réglage de rowHeight. Toutes ces opérations sont animées par défaut.

Comment puis-je détecter que l'animation est terminée lors de l'utilisation beginUpdates/endUpdates?

pixelfreak
la source
1
FYI: Cela s'applique -scrollToRowAtIndexPath:atScrollPosition:animated:également.
Ben Lachman

Réponses:

292

Et ça?

[CATransaction begin];

[CATransaction setCompletionBlock:^{
    // animation has finished
}];

[tableView beginUpdates];
// do some work
[tableView endUpdates];

[CATransaction commit];

Cela fonctionne car les animations tableView utilisent des CALayeranimations en interne. Autrement dit, ils ajoutent les animations à toute ouverture CATransaction. S'il n'y a pas d'ouverture CATransaction(le cas normal), alors une est implicitement commencée, qui se termine à la fin de la boucle d'exécution actuelle. Mais si vous en commencez un vous-même, comme c'est le cas ici, il utilisera celui-là.

Rudolf Adamkovič
la source
7
Cela n'a pas fonctionné pour moi. Le bloc d'achèvement est appelé pendant que l'animation est toujours en cours d'exécution.
mrvincenzo
2
@MrVincenzo Avez-vous défini l' completionBlockavant beginUpdateset endUpdatescomme dans l'extrait de code?
Rudolf Adamkovič
2
[CATransaction commit]devrait être appelé après pas avant [tableView endUpdates].
Rudolf Adamkovič
6
Le bloc de complétion n'est jamais appelé si une cellule contient une vue avec des animations, c'est-à-dire UIActivityIndicatorView.
markturnip
3
Après tout ce temps, je n'ai jamais su cela. Or pur !! Rien de pire que de voir une belle animation se faire tuer avec le mal nécessaire de tableView.reloadData ().
DogCoffee
31

Version Swift


CATransaction.begin()

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

tableView.beginUpdates()
tableView.endUpdates()

CATransaction.commit()
Michael
la source
6

Si vous ciblez iOS 11 et supérieur, vous devez utiliser à la UITableView.performBatchUpdates(_:completion:)place:

tableView.performBatchUpdates({
    // delete some cells
    // insert some cells
}, completion: { finished in
    // animation complete
})
Robert
la source
5

Une solution possible pourrait être d'hériter de l'UITableView sur lequel vous appelez endUpdateset de l'écraser setContentSizeMethod, car UITableView ajuste sa taille de contenu pour correspondre aux lignes ajoutées ou supprimées. Cette approche devrait également fonctionner reloadData.

Pour s'assurer qu'une notification n'est envoyée qu'après avoir endUpdatesété appelée, on peut également écraser endUpdateset définir un indicateur à cet endroit.

// 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;
    }
}
Antoni
la source
5

Vous pouvez inclure vos opérations dans le bloc d'animation UIView comme suit:

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

Crédits à https://stackoverflow.com/a/12905114/634940 .

Zdenek
la source
4
Ce code n'a pas fonctionné pour moi. Le bloc d'achèvement a été appelé après endUpdates, mais avant la fin des animations.
Tim Camber
1
Cela ne marche pas. L'animation tableview a cessé de fonctionner lors de l'application de cet exemple.
Primoz990
Essayez de prolonger la durée (comme 0,3 sec) - cela devrait fonctionner (cela a fonctionné pour moi)
emem
1

Je n'ai pas encore trouvé de bonne solution (à court de sous-classer UITableView). J'ai décidé de l'utiliser performSelector:withObject:afterDelay:pour le moment. Pas idéal, mais fait le travail.

MISE À JOUR : Il semble que je puisse utiliser scrollViewDidEndScrollingAnimation:à cette fin (c'est spécifique à mon implémentation, voir commentaire).

pixelfreak
la source
1
scrollViewDidEndScrollingAnimationest appelé uniquement en réponse à setContentOffsetetscrollRectToVisible
samvermette
@samvermette Eh bien, dans mon cas, lorsque beginUpdates / endUpdates sont appelés, l'UITableView anime toujours le défilement. Mais cela est spécifique à ma mise en œuvre, donc je suis d'accord que ce n'est pas la meilleure réponse, mais cela fonctionne pour moi.
pixelfreak
Il semble qu'il est possible d'inclure les mises à jour dans un bloc d'animation UIView. Voir ma réponse ci-dessous: stackoverflow.com/a/14305039/634940 .
Zdenek
0

Vous pouvez utiliser tableView:willDisplayCell:forRowAtIndexPath:comme:

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

Mais cela sera également appelé lorsqu'une cellule qui est déjà dans le tableau passe de l'écran hors de l'écran à l'écran, de sorte que ce n'est peut-être pas exactement ce que vous recherchez. Je viens de regarder toutes les méthodes UITableViewet UIScrollViewdéléguer et il ne semble pas y avoir quoi que ce soit à gérer juste après l'insertion d'une cellule.


Pourquoi ne pas simplement appeler la méthode que vous souhaitez appeler lorsque l'animation se termine après le 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];
    }
}
chown
la source
Merci, chown. Ne pensez pas que willDisplayCellcela fonctionnera. J'ai besoin que cela se produise après l'animation car je n'ai besoin de faire une mise à jour visuelle qu'une fois que tout est réglé.
pixelfreak
np @pixelfreak, si je pense à une autre façon de faire cela, je vous le ferai savoir.
chown