避免reloadItemsAtIndexPaths之后的UICollectionView动画


Answers:


231

值得注意的是,如果您的目标是iOS 7及更高版本,则可以使用newUIView方法performWithoutAnimation:。我怀疑这实际上与这里的其他答案(暂时禁用UIView动画/ Core Animation动作)的作用大致相同,但语法很好。

因此,对于这个问题尤其如此...

目标C:

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


迅速:

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


当然,该原理可以应用于您要确保更改没有变化的任何情况。


3
在iOS 7+上,这比我接受的答案更好。
菲利普·萨布林

它不仅禁用了所有动画的集合视图
user2159978 2014年

10
@ user2159978没错;这里的所有答案都会暂时禁用UIView动画。仅对从传入块内启动的动作禁用动画,在这种情况下,仅重新加载集合视图。这是对所提问题的完全有效的答案,因此我认为不应对此投反对票。
Stuart

难以置信的解决方案。使用此大小而不是animateWithDuration:0可以防止在使用没有itemSize的自动调整大小的集合视图单元格时出现快速但可见的布局故障
Ethan Gill

1
此解决方案在iOS 14上不再起作用,在此块中,我使用reloadData,但在iOS 13上有效
Maray97

161

您也可以尝试以下方法:

UICollectionView *collectionView;

...

[UIView setAnimationsEnabled:NO];

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

编辑:

我还发现,如果包装performBatchUpdates在UIView动画块中,则将使用UIView动画而不是默认动画,因此可以将动画持续时间设置为0,如下所示:

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

如果要在插入和删除过程中使用iOS 7弹性动画,这将特别酷!


1
谢谢。这对于向uicollectionview单元格添加秒表计时器非常有帮助。
hatunike

辉煌!我将它与insertCells一起使用,并与键盘一起使用,以在插入单元格后将collectionView设置为新的偏移量。
David H

5
正如彼得所说,这会干扰其他动画。相反,您应该在完成块之外调用[UIView setAnimationsEnabled:YES]。这样,您只能阻止该1个动画。
Adlai Holler 2014年

2
我添加了另一种方法,它的作用完全相同。
萨姆

1
包裹performBatchUpdates内部真是太好了animateWithDuration!谢谢你的提示!
罗伯特

19

调用reloadItemsAtIndexPaths之后,UICollectionView会设置动画项目(淡入动画)。

有办法避免这种动画吗?

iOS 6

我假设您正在使用FlowLayout。由于您要摆脱淡入淡出的动画,请尝试以下操作:

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
    }

}

这是一个非常老的问题,因此您可能不再针对iOS 6。我亲自在tvOS 11上工作,并遇到了相同的问题,所以对于那些遇到相同问题的人来说,这就是这里。


1
对于“哇,效果很好!”的效果,有3或4条评论。有人决定删除评论。我认为,实际上,这是实现这一目标的现代且非庸俗的方式,而这些评论就是对这一认识的认可。
Matt Mc

8

在UICollectionView上编写了一个类别来实现这一目的。技巧是在重新加载时禁用所有动画:

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

[self reloadItemsAtIndexPaths:indexPaths];

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

我也从苹果公司那里得到答复,说它不应该做任何动画,如果这样的话,那是一个错误。我不知道我做错了什么还是一个错误。
Marcin 2013年

2
您也可以CATransaction.setDisableActions(true)为此做一个简写。
CIFilter


5

这是Swift 3版本,performBatchUpdates没有动画UICollectionView。我发现此方法对我而言更好,collectionView.reloadData()因为它减少了插入记录时的单元交换。

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

只需增加我的0.02美元,我就尝试了所选答案的两个版本,并且原始方式可以更好地达到我的目的。我正在使用一个无限滚动的日历视图,该视图允许用户在给定的星期输入日历,然后来回滑动并选择单个日期以过滤列表。

在我的实现中,为了使事情在较旧的设备上保持高性能,代表日历视图的日期数组必须保持相对较小,这意味着要保留大约5周的日期,而用户要在第3周居中。使用第二种方法的问题是,还有第二个步骤,您必须在没有动画的情况下将集合视图滚动回到中间,这会由于某种原因而使基本动画被阻止,从而使外观非常参差不齐。

我的代码:

[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)
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.