¿Cómo detectar que la animación ha terminado en UITableView beginUpdates / endUpdates?

116

Estoy insertando / eliminando la celda de la tabla usando insertRowsAtIndexPaths/deleteRowsAtIndexPathsenvuelto en beginUpdates/endUpdates. También lo estoy usando beginUpdates/endUpdatesal ajustar rowHeight. Todas estas operaciones están animadas por defecto.

¿Cómo puedo detectar que la animación ha terminado cuando se usa beginUpdates/endUpdates?

pixelfreak
fuente
1
FYI: Esto también se aplica a -scrollToRowAtIndexPath:atScrollPosition:animated:.
Ben Lachman

Respuestas:

292

¿Qué pasa con esto?

[CATransaction begin];

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

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

[CATransaction commit];

Esto funciona porque las animaciones de tableView usan CALayeranimaciones internamente. Es decir, añaden las animaciones a cualquier open CATransaction. Si no CATransactionexiste ninguna apertura (el caso normal), entonces se inicia implícitamente una, que finaliza al final del ciclo de ejecución actual. Pero si comienza uno usted mismo, como se hace aquí, entonces usará ese.

Rudolf Adamkovič
fuente
7
No funcionó para mí. Se llama al bloque de finalización mientras la animación aún se está ejecutando.
mrvincenzo
2
@MrVincenzo ¿Estableciste el completionBlockantes beginUpdatesy el me endUpdatesgusta en el fragmento?
Rudolf Adamkovič
2
[CATransaction commit]debe llamarse después, no antes [tableView endUpdates].
Rudolf Adamkovič
6
El bloque de finalización nunca se llama si una celda contiene una vista con animaciones, es decir, UIActivityIndicatorView.
markturnip
3
Después de todo este tiempo, nunca supe esto. Oro puro !! No hay nada peor que ver cómo se mata una bonita animación con el mal necesario de tableView.reloadData ().
DogCoffee
31

Versión rápida


CATransaction.begin()

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

tableView.beginUpdates()
tableView.endUpdates()

CATransaction.commit()
Miguel
fuente
6

Si tiene como objetivo iOS 11 y superior, debería usar UITableView.performBatchUpdates(_:completion:)en su lugar:

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

Una posible solución podría ser heredar del UITableView en el que llama endUpdatesy sobrescribirlo setContentSizeMethod, ya que UITableView ajusta el tamaño de su contenido para que coincida con las filas agregadas o eliminadas. Este enfoque también debería funcionar reloadData.

Para asegurarse de que una notificación se envíe solo después de que endUpdatesse llame, también se podría sobrescribir endUpdatesy establecer una bandera allí.

// 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
fuente
5

Puede incluir su (s) operación (es) en el bloque de animación UIView así:

- (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éditos a https://stackoverflow.com/a/12905114/634940 .

Zdenek
fuente
4
Este código no funcionó para mí. El bloque de finalización se llamó después endUpdates, pero antes de que se completaran las animaciones.
Tim Camber
1
Esto no funciona. La animación de la vista de tabla dejó de funcionar al aplicar esta muestra.
Primoz990
Intente alargar la duración (como 0,3 segundos); debería funcionar (a mí me funcionó)
emem
1

Todavía no he encontrado una buena solución (salvo subclasificar UITableView). Decidí usarlo performSelector:withObject:afterDelay:por ahora. No es ideal, pero hace el trabajo.

ACTUALIZACIÓN : Parece que puedo usarlo scrollViewDidEndScrollingAnimation:para este propósito (esto es específico para mi implementación, ver comentario).

pixelfreak
fuente
1
scrollViewDidEndScrollingAnimationsolo se llama en respuesta a setContentOffsetyscrollRectToVisible
samvermette
@samvermette Bueno, en mi caso, cuando se llama a beginUpdates / endUpdates, UITableView siempre anima-scroll. Pero esto es específico de mi implementación, así que estoy de acuerdo en que no es la mejor respuesta, pero funciona para mí.
pixelfreak
Parece que es posible incluir actualizaciones en un bloque de animación UIView. Vea mi respuesta a continuación: stackoverflow.com/a/14305039/634940 .
Zdenek
0

Puedes usar tableView:willDisplayCell:forRowAtIndexPath:como:

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

Pero esto también se llamará cuando una celda que ya está en la tabla se mueva de fuera de la pantalla a la pantalla, por lo que puede que no sea exactamente lo que está buscando. Acabo de revisar todos los métodos UITableViewy UIScrollViewdelegate y no parece haber nada que manejar justo después de que se inserta la animación de una celda.


¿Por qué no simplemente llamar al método que desea que se llame cuando la animación finalice después de 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
fuente
Gracias, chown. No creo willDisplayCellque funcione. Necesito que suceda después de la animación porque necesito hacer una actualización visual solo después de que todo se resuelva.
pixelfreak
np @pixelfreak, si pienso en alguna otra forma de hacer esto, te lo haré saber.
chown