Como detectar se a animação terminou em UITableView beginUpdates / endUpdates?

116

Estou inserindo / excluindo célula da tabela usando insertRowsAtIndexPaths/deleteRowsAtIndexPathsagrupado beginUpdates/endUpdates. Também estou usando beginUpdates/endUpdatesao ajustar rowHeight. Todas essas operações são animadas por padrão.

Como posso detectar que a animação terminou ao usar beginUpdates/endUpdates?

pixelfreak
fonte
1
FYI: Isso se aplica -scrollToRowAtIndexPath:atScrollPosition:animated:também.
Ben Lachman

Respostas:

292

Que tal isso?

[CATransaction begin];

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

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

[CATransaction commit];

Isso funciona porque as animações tableView usam CALayeranimações internamente. Ou seja, eles adicionam as animações a qualquer aberto CATransaction. Se não CATransactionexistir nenhuma abertura (o caso normal), então uma é implicitamente iniciada, que é finalizada no final do loop de execução atual. Mas se você mesmo começar um, como é feito aqui, ele o usará.

Rudolf Adamkovič
fonte
7
Não funcionou para mim. O bloco de conclusão é chamado enquanto a animação ainda está em execução.
mrvincenzo de
2
@MrVincenzo Você definiu o completionBlockantes beginUpdatese endUpdatesgostou do snippet?
Rudolf Adamkovič
2
[CATransaction commit]deve ser chamado depois, não antes [tableView endUpdates].
Rudolf Adamkovič
6
O bloco de conclusão nunca é chamado se uma célula contém uma visualização com animações, ou seja, UIActivityIndicatorView.
markturnip
3
Depois de todo esse tempo, eu nunca soube disso. Ouro puro !! Nada pior do que ver uma bela animação sendo eliminada com a maldade necessária de tableView.reloadData ().
DogCoffee de
31

Versão Swift


CATransaction.begin()

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

tableView.beginUpdates()
tableView.endUpdates()

CATransaction.commit()
Michael
fonte
6

Se você está direcionando o iOS 11 e superior, deve usar UITableView.performBatchUpdates(_:completion:):

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

Uma possível solução poderia ser herdar do UITableView no qual você chama endUpdatese sobrescrever seu setContentSizeMethod, já que UITableView ajusta seu tamanho de conteúdo para corresponder às linhas adicionadas ou removidas. Essa abordagem também deve funcionar para reloadData.

Para garantir que uma notificação seja enviada somente após endUpdatesser chamada, também é possível sobrescrever endUpdatese definir um sinalizador lá.

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

Você pode incluir sua (s) operação (ões) no bloco de animação UIView assim:

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

Zdenek
fonte
4
Este código não funcionou para mim. O bloco de conclusão foi chamado depois endUpdates, mas antes que as animações fossem concluídas.
Tim Camber
1
Isso não funciona. A animação tableview parou de funcionar, ao aplicar este exemplo.
Primoz990
Tente aumentar a duração (como 0,3 seg) - deve funcionar (funcionou para mim)
emem
1

Ainda não encontrei uma boa solução (exceto subclassificar UITableView). Decidi usar performSelector:withObject:afterDelay:por agora. Não é o ideal, mas dá conta do recado.

ATUALIZAÇÃO : Parece que posso usar scrollViewDidEndScrollingAnimation:para este propósito (isto é específico para minha implementação, ver comentário).

pixelfreak
fonte
1
scrollViewDidEndScrollingAnimationé chamado apenas em resposta a setContentOffsetescrollRectToVisible
samvermette
@samvermette Bem, no meu caso, quando beginUpdates / endUpdates são chamados, o UITableView sempre animate-scroll. Mas isso é específico para minha implementação, então concordo que não é a melhor resposta, mas funciona para mim.
pixelfreak
Parece que é possível incluir as atualizações em um bloco de animação UIView. Veja minha resposta abaixo: stackoverflow.com/a/14305039/634940 .
Zdenek
0

Você pode 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];
}

Mas isso também será chamado quando uma célula que já está na tabela se mover de fora da tela para a tela, de forma que pode não ser exatamente o que você está procurando. Acabei de examinar todos os métodos UITableViewe UIScrollViewdelegate e não parece haver nada para tratar logo depois que uma célula é inserida na animação.


Por que não apenas chamar o método que você deseja que seja chamado quando a animação terminar depois 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
fonte
Obrigado, chown. Não pense que willDisplayCellvai funcionar. Preciso que isso aconteça depois da animação porque preciso fazer uma atualização visual somente depois que tudo se acalmar.
pixelfreak,
np @pixelfreak, se eu pensar em outra maneira de fazer isso, eu te aviso.
chown em