在 reloadItemsAtIndexPath 之后避免 UICollectionView 的动画

调用 reloadItemsAtIndexPath 之后的 UICollectionView 动画项(淡出动画)。

有办法避免这种动画吗?

IOS6

80683 次浏览

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

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!

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


}

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

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.

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

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


})
}
}

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.

 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)
}

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

self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)