95

UICollectionView animate items after reloadItemsAtIndexPaths is called (fade animation).

Is there a way to avoid this animation?

iOS 6

Marcin
  • 3,279
  • 4
  • 29
  • 47

10 Answers10

239

It's worth noting that if you're targeting iOS 7 and above, you can use the new UIView method performWithoutAnimation:. I suspect that under the hood this is doing much the same as the other answers here (temporarily disabling UIView animations / Core Animation actions), but the syntax is nice and clean.

So for this question in particular...

Objective-C:

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


Swift:

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


Of course this principle can be applied for any situation that you want to ensure a change is not animated.

Stuart
  • 34,797
  • 19
  • 93
  • 135
  • 3
    This worked better than the accepted answer for me on iOS 7+. – Philippe Sabourin Nov 21 '14 at 22:10
  • It disables all the animations not only for collection view – user2159978 Dec 04 '14 at 13:32
  • 11
    @user2159978 That's correct; all the answers here temporarily disable UIView animations. The animations are only disabled for actions initiated from within the passed-in block, in this case just the collection view's reload. It is a perfectly valid answer to the question being asked, so I don't think it deserves a down vote. – Stuart Dec 04 '14 at 22:51
  • Incredible solution. Using this instead of animateWithDuration:0 prevents a quick but visible layout glitch when using self-sizing collection view cells with no itemSize – Ethan Gill Oct 17 '17 at 21:49
  • 1
    This solution doesn't work anymore on iOS 14, inside this block I use reloadData, but with iOS 13 it worked – Maray97 Sep 30 '20 at 20:55
161

You could also try this:

UICollectionView *collectionView;

...

[UIView setAnimationsEnabled:NO];

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

Edit:

I have also found that if you wrap performBatchUpdates in a UIView animation block, the UIView animation is used instead of the default animation, so you can just set the animation duration to 0, like so:

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

This is extra cool if you want to use iOS 7 springy animations during inserts and deletes!

Sam
  • 5,822
  • 1
  • 22
  • 27
  • 1
    Thank you. This was very helpful in adding stopwatch timers to uicollectionview cells. – hatunike Feb 26 '13 at 00:06
  • Brilliant! I used this with insertCells, and with the keyboard up, to animate the collectionView to a new offset after the cell was inserted. – David H Oct 11 '13 at 22:56
  • conflicts with other animations :/ – Peter Lapisu Jan 25 '14 at 16:49
  • 6
    As Peter says, this interferes with other animations. Instead, you should call [UIView setAnimationsEnabled:YES] outside of the completion block. That way you only prevent that 1 animation. – Adlai Holler Jan 29 '14 at 03:41
  • 2
    I added an alternative method, which does exactly the same thing. – Sam Mar 05 '14 at 12:27
  • 1
    Wrapping ```performBatchUpdates``` inside ```animateWithDuration``` is brilliant! Thanks for the tip! – Robert Oct 07 '14 at 18:24
  • WARNING. This method DOESN'T WORK PROPERLY!!! Try to scroll while this method is executed. At least in my case when I should reload layout too it breaks content size of collection view – user2159978 Dec 04 '14 at 13:30
  • I don't know why but it's perfectly fix my flashing issue with UIView animateWithDuration:0 animations. Thank you very much. – BollMose Dec 28 '15 at 03:02
  • Second one is much safer! :) – Magoo Apr 25 '16 at 16:19
  • Visually, the second method does not work as well as the first method. – Just a coder Jul 08 '16 at 14:50
21

UICollectionView animate items after reloadItemsAtIndexPaths is called (fade animation).

Is there a way to avoid this animation?

iOS 6

I assume you're using a FlowLayout. Since you're trying to get rid of the fade animation, try this:

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
    }

}

This is a very old question, so you're probably not targeting iOS 6 anymore. I was personally working on tvOS 11 and had the same question, so this is here for anyone who comes along with the same problem.

Matt Mc
  • 7,604
  • 5
  • 47
  • 82
  • 1
    There were 3 or 4 comments on this answer to the effect of "Wow this works really well!" Someone decided to remove the comments. I think, factually, this is the modern and non-hacky way of accomplishing this, and those comments were a recognition of this. – Matt Mc Jun 16 '19 at 00:35
8

I wrote a category on UICollectionView to do just that. The trick is to disable all animations while reloading:

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

[self reloadItemsAtIndexPaths:indexPaths];

if (!animated) {
    [CATransaction commit];
}
  • I also get response from Apple that this should not do any animation, if so it's a bug. I don't know if I'm doing something wrong or it's a bug. – Marcin Jan 19 '13 at 13:05
  • 2
    You can also just do `CATransaction.setDisableActions(true)` as a shorthand for this. – CIFilter May 09 '17 at 00:05
5
extension UICollectionView {
    func reloadWithoutAnimation(){
        CATransaction.begin()
        CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
        self.reloadData()
        CATransaction.commit()
    }
}
Amjad Tubasi
  • 309
  • 4
  • 5
5

Here is a Swift 3 version to performBatchUpdates without animation to a UICollectionView. I found this to work better for me than collectionView.reloadData() because it reduced cell swapping when records were inserted.

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

        })
    }
}
markhorrocks
  • 1,206
  • 12
  • 59
  • 113
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];
    }

}
Peter Lapisu
  • 18,394
  • 14
  • 107
  • 163
1

Just to add my $0.02, I tried both versions of the selected answer, and the original way worked better for my purposes. I am working on an infinite scrolling calendar view that allows for a user to enter the calendar at a given week and then swipe back and forth and select individual days for filtering a list.

In my implementation, in order to keep things performant on older devices the array of dates that represent the calendar view has to be kept relatively small which means holding about 5 weeks worth of dates, with the user in the middle at the 3rd week. The issue with using the second approach is, there's a second step where you have to scroll the collection view back to the middle without an animation, which makes for a very jagged appearance for some reason with the blocked base animation.

My Code:

[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];
Matt S.
  • 1,860
  • 13
  • 17
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)
    }
0

Instead of using reloadData() try the following to reload all visible cells without animation.

self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)
Ben
  • 2,044
  • 3
  • 22
  • 38