Evite a animação de UICollectionView após reloadItemsAtIndexPaths


92

UICollectionView animar itens após reloadItemsAtIndexPaths ser chamado (animação de fade).

Existe uma maneira de evitar essa animação?

iOS 6

Respostas:


234

É importante observar que, se você está direcionando o iOS 7 e superior, pode usar o novo UIViewmétodo performWithoutAnimation:. Suspeito que, por baixo do capô, isso está fazendo quase o mesmo que as outras respostas aqui (desativando temporariamente as UIViewanimações / ações do Core Animation), mas a sintaxe é boa e limpa.

Então, para esta questão em particular ...

Objective-C:

[UIView performWithoutAnimation:^{
    [self.collectionView reloadItemsAtIndexPaths:indexPaths];
}];


Rápido:

UIView.performWithoutAnimation {
    self.collectionView.reloadItemsAtIndexPaths(indexPaths)
}


É claro que esse princípio pode ser aplicado a qualquer situação em que você deseja garantir que uma mudança não seja animada.


3
Funcionou melhor do que a resposta aceita para mim no iOS 7 ou superior.
Philippe Sabourin

Ele desativa todas as animações não apenas para a visualização da coleção
user2159978

10
@ user2159978 Correto; todas as respostas aqui desabilitam temporariamente as animações UIView. As animações são desabilitadas apenas para ações iniciadas de dentro do bloco passado, neste caso apenas para recarregar a visualização da coleção. É uma resposta perfeitamente válida à pergunta que está sendo feita, então não acho que mereça uma votação negativa.
Stuart

Solução incrível. Usar isso em vez de animateWithDuration: 0 evita uma falha de layout rápida, mas visível, ao usar células de exibição de coleção com autodimensionamento sem itemSize
Ethan Gill

1
Esta solução não funciona mais no iOS 14, dentro deste bloco eu uso reloadData, mas com o iOS 13 funcionou
Maray97

161

Você também pode tentar isto:

UICollectionView *collectionView;

...

[UIView setAnimationsEnabled:NO];

[collectionView performBatchUpdates:^{
    [collectionView reloadItemsAtIndexPaths:indexPaths];
} completion:^(BOOL finished) {
    [UIView setAnimationsEnabled:YES];
}];

Editar:

Também descobri que, se você envolver performBatchUpdatesem um bloco de animação UIView, a animação UIView será usada em vez da animação padrão, então você pode apenas definir a duração da animação para 0, assim:

[UIView animateWithDuration:0 animations:^{
    [collectionView performBatchUpdates:^{
        [collectionView reloadItemsAtIndexPaths:indexPaths];
    } completion:nil];
}];

Isso é muito legal se você quiser usar animações elásticas do iOS 7 durante inserções e exclusões!


1
Obrigado. Isso foi muito útil para adicionar cronômetros às células do uicollectionview.
hatunike

Brilhante! Usei isso com insertCells e com o teclado para cima para animar a collectionView para um novo deslocamento depois que a célula foi inserida.
David H

5
Como diz Peter, isso interfere em outras animações. Em vez disso, você deve chamar [UIView setAnimationsEnabled: YES] fora do bloco de conclusão. Dessa forma, você evita apenas aquela 1 animação.
Adlai Holler

2
Eu adicionei um método alternativo, que faz exatamente a mesma coisa.
Sam

1
Embrulhar performBatchUpdatesdentro animateWithDurationé brilhante! Obrigado pela dica!
Robert,

20

UICollectionView animar itens após reloadItemsAtIndexPaths ser chamado (animação de fade).

Existe uma maneira de evitar essa animação?

iOS 6

Presumo que você esteja usando um FlowLayout. Já que você está tentando se livrar da animação de esmaecimento, tente isto:

import UIKit

class NoFadeFlowLayout: UICollectionViewFlowLayout {

    override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attrs = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)
        attrs?.alpha = 1.0
        return attrs
    }

    override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attrs = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)
        attrs?.alpha = 1.0
        return attrs
    }

}

Essa é uma pergunta muito antiga, então provavelmente você não está mais voltado para o iOS 6. Eu estava trabalhando pessoalmente no tvOS 11 e tinha a mesma dúvida, então este está aqui para quem vem com o mesmo problema.


1
Houve 3 ou 4 comentários sobre esta resposta no sentido de "Uau, isso funciona muito bem!" Alguém decidiu remover os comentários. Acho que, na verdade, essa é a maneira moderna e não hacky de fazer isso, e esses comentários foram um reconhecimento disso.
Matt Mc

8

Eu escrevi uma categoria em UICollectionView para fazer exatamente isso. O truque é desativar todas as animações durante o recarregamento:

if (!animated) {
    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
}

[self reloadItemsAtIndexPaths:indexPaths];

if (!animated) {
    [CATransaction commit];
}

Também recebo resposta da Apple de que isso não deve fazer nenhuma animação, se assim for, é um bug. Não sei se estou fazendo algo errado ou é um bug.
Marcin

2
Você também pode fazer apenas CATransaction.setDisableActions(true)uma abreviação para isso.
CIFilter

5
extension UICollectionView {
    func reloadWithoutAnimation(){
        CATransaction.begin()
        CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
        self.reloadData()
        CATransaction.commit()
    }
}

esta é a sintaxe do swift 3
Amjad Tubasi

funcionou bem, ainda melhor do que UIView.performWithoutAnimation
Lance Samaria

5

Aqui está uma versão do Swift 3 performBatchUpdatessem animação para a UICollectionView. Descobri que isso funcionou melhor para mim do que collectionView.reloadData()porque reduziu a troca de células quando os registros foram inseridos.

func appendCollectionView(numberOfItems count: Int){

        // calculate indexes for the items to be added
        let firstIndex = dataItems.count - count
        let lastIndex = dataItems.count - 1

        var indexPaths = [IndexPath]()
        for index in firstIndex...lastIndex {
            let indexPath = IndexPath(item: index, section: 0)
            indexPaths.append(indexPath)
        }

   UIView.performWithoutAnimation {

        self.collectionView.performBatchUpdates({ () -> Void in
            self.collectionView.insertItems(at: indexPaths)
        }, completion: { (finished) -> Void in

        })
    }
}

2
- (void)reloadCollectionViewAnimated:(BOOL)animated  {

    if (animated) {
        [self.collectionView performBatchUpdates:^{
            [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
        } completion:^(BOOL finished) {

        }];
    } else {
        [CATransaction begin];
        [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
        [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
        [CATransaction commit];
    }

}

1

Apenas para adicionar meus $ 0,02, tentei as duas versões da resposta selecionada, e a forma original funcionou melhor para meus objetivos. Estou trabalhando em uma visualização de calendário com rolagem infinita que permite ao usuário entrar no calendário em uma determinada semana e, em seguida, deslizar para frente e para trás e selecionar dias individuais para filtrar uma lista.

Na minha implementação, para manter o desempenho das coisas em dispositivos mais antigos, o conjunto de datas que representam a visualização do calendário deve ser mantido relativamente pequeno, o que significa manter cerca de 5 semanas de datas, com o usuário no meio na 3ª semana. O problema de usar a segunda abordagem é que há uma segunda etapa em que você precisa rolar a exibição da coleção de volta para o meio sem uma animação, o que torna uma aparência muito irregular por algum motivo com a animação de base bloqueada.

Meu código:

[UIView setAnimationsEnabled:NO];
[self.collectionView performBatchUpdates:^{
    [self.collectionView deleteItemsAtIndexPaths:indexPathDeleteArray];
    [self.collectionView insertItemsAtIndexPaths:indexPathAddArray];

} completion:NULL];
[UIView setAnimationsEnabled:YES];

NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:14 inSection:0];
[self.collectionView scrollToItemAtIndexPath:newIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];

0
 func reloadRowsWithoutAnimation(at indexPaths: [IndexPath]) {
        let contentOffset = collectionView.contentOffset
        UIView.setAnimationsEnabled(false)
        collectionView.performBatchUpdates {
            collectionView.reloadItems(at: indexPaths)
        }
        UIView.setAnimationsEnabled(true)
        collectionView.setContentOffset(contentOffset, animated: false)
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.