53

我正在开发一个自定义UICollectionViewLayout显示按天/周/月组织的单元格。

它滚动不流畅,看起来滞后是由于[UICollectionView _updateVisibleCellsNow]在每个渲染循环上被调用造成的。

< 30 个项目的性能还可以,但在 100 个或更多时,它非常慢。这是 UICollectionView 和自定义布局的限制,还是我没有为视图提供足够的信息来正确执行?

来源:https ://github.com/oskarhagberg/calendarcollection

布局:https ://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarWeekLayout.m

数据源和委托:https ://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m

时间简介: 时间配置文件 - 自定义布局

更新

也许是徒劳的?一些具有大致相同数量的单元格/屏幕的普通测试UICollectionViewControllerUICollectionViewFlowLayout产生相似的时间分布。

时间配置文件 - 标准流程布局

我觉得它应该能够一次处理约 100 个简单的不透明单元而没有抖动。我错了吗?

4

10 回答 10

93

也不要忘记尝试栅格化单元格的图层:

cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

没有那个我有 10 fps,有 55 fps !我对 GPU 和合成模型不是很熟悉,所以它到底做了什么我并不完全清楚,但基本上它只在一个位图(而不是每个子视图一个位图?)中扁平化所有子视图的渲染。无论如何,我不知道它是否有一些缺点,但它的速度要快得多!

于 2013-06-27T08:08:44.453 回答
18

我一直在对 UICollectionView 的性能进行大量调查。结论很简单。大量单元的性能很差。



编辑:抱歉,只需重新阅读您的帖子,您拥有的单元格数量应该没问题(请参阅我的其余评论),因此单元格复杂性也可能是一个问题。

如果您的设计支持它,请检查:

  1. 每个单元格都是不透明的。

  2. 单元格内容剪辑到边界。

  3. 单元格坐标位置不包含小数值(例如,始终计算为整个像素)

  4. 尽量避免重叠单元格。

  5. 尽量避免阴影。



原因其实很简单。很多人不理解这一点,但值得理解的是:UIScrollViews 没有使用 Core Animation 来滚动。我天真地认为他们涉及一些秘密的滚动动画“酱”,只是不时地要求代表们偶尔更新以检查状态。但实际上滚动视图是根本不直接应用任何绘图行为的对象。它们实际上只是一个应用数学函数抽象它们包含的 UIViews 坐标位置的类,因此 Views 坐标被视为相对于抽象 contentView 平面而不是相对于包含视图的原点。滚动视图将根据用户输入更新抽象滚动平面的位置(例如

现在,如果您要生成自己的集合视图布局对象,理论上,您可以生成一个 100% 反转底层滚动视图应用的数学转换的对象。这会很有趣但没用,因为当你滑动时,细胞似乎根本没有移动。但是我提出了这种可能性,因为它说明了集合视图布局对象与集合视图对象本身的操作与底层滚动视图非常相似。例如,它只是提供了一个机会,可以对要显示的视图的属性进行逐帧的附加数学转换,并且主要是位置属性的简单转换。

只有在插入或删除新单元格、移动或重新加载时才会涉及 CoreAnimation;通常通过调用:

- (void)performBatchUpdates:(void (^)(void))updates
                 completion:(void (^)(BOOL finished))completion

UICollectionView 为每一帧滚动请求单元格 layoutAttributes 并且为每一帧布局每个可见视图。UIView 是针对灵活性而非性能进行优化的丰富对象。每次布局时,系统都会进行许多测试来检查它的 alpha、zIndex、子视图、剪辑属性等。列表很长。这些检查以及任何由此产生的视图更改都将针对每个框架的每个集合视图项进行。

为确保良好的性能,所有逐帧操作都需要在 17 毫秒内完成。[由于您拥有的单元格数量,这根本不会发生]将这个条款括起来,因为我已经重新阅读了您的帖子,并且我意识到我误读了它。有了你拥有的细胞数量,应该不会有性能问题。我个人发现使用香草单元进行简化测试,只包含一个缓存图像,在 iPad 3 上测试的限制是大约 784 个屏幕单元,然后性能开始下降到 50fps 以下。

在实践中,您需要保持低于此值。

就我个人而言,我正在使用自己的自定义布局对象,并且需要比 UICollectionView 提供的更高的性能。不幸的是,我没有运行上面的简单测试,直到开发路径的某个方向,我意识到存在性能问题。所以我将重新设计 UICollectionView 的开源后端端口 PSTCollectionView。我认为有一种解决方法可以实现,因此,仅对于一般滚动,每个单元格项目的层都是使用绕过每个 UIView 单元格布局的操作编写的。这对我有用,因为我有自己的布局对象,并且我知道何时需要布局,并且我有一个巧妙的技巧,可以让 PSTCollectionView 在此时回退到其正常操作模式。我检查了调用顺序,看起来并不太复杂,它也似乎完全不可行。但可以肯定的是,它不是微不足道的,在我确认它会起作用之前,必须进行一些进一步的测试。

于 2013-05-08T09:02:59.447 回答
8

更多可能有用的观察结果:

我能够重现这个问题,使用流布局一次可以看到大约 175 个项目:它在模拟器中平滑滚动,但在 iPhone 5 上像地狱一样滞后。确保它们是不透明的等。

在此处输入图像描述

最终花费最多时间的似乎是使用可变的 NSDictionary inside _updateVisibleCellsNow。既复制字典,又按键查找项目。键似乎是 UICollectionViewItemKey 并且该[UICollectionViewItemKey isEqual:]方法是所有方法中最耗时的方法。UICollectionViewItemKey 至少包含type,identifierindexPath属性,其中包含的属性 indexPath 比较[NSIndexPath isEqual:]耗时最多。

据此,我猜测 UICollectionViewItemKey 的哈希函数可能缺少,因为isEqual:在字典查找期间经常调用它。许多项目可能以相同的哈希结束(或在相同的哈希桶中,不确定 NSDictionary 是如何工作的)。

出于某种原因,与每个部分包含 7 个项目的许多部分相比,1 个部分中的所有项目更快。可能是因为它在 NSIndexPath isEqual 上花费了太多时间,如果行首先发生差异,那会更快,或者 UICollectionViewItemKey 得到更好的哈希值。

老实说,UICollectionView 在每个滚动帧都执行繁重的字典工作感觉真的很奇怪,正如之前提到的,每个帧更新需要 <16ms 以避免延迟。我想知道这么多的字典查找是否是:

  • 一般的 UICollectionView 操作确实需要
  • 那里支持一些很少使用的边缘情况,并且可以在大多数布局中关闭
  • 一些尚未修复的未优化内部代码
  • 我们这边的一些错误

希望今年夏天在 WWDC 期间我们会看到一些改进,或者如果这里的其他人可以弄清楚如何解决它。

于 2013-05-09T16:36:32.757 回答
7

这是转换为 Swift 3的Altimac 的答案:

cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale

此外,应该注意,此代码位于 cellForItemAtIndexPath 的 collectionView 委托方法中。

另一个提示 - 要查看应用程序的每秒帧数 (FPS),请在 Instruments 下打开 Core Animation(参见屏幕截图)。

在此处输入图像描述

于 2015-11-09T18:36:53.453 回答
6

问题不在于您在集合视图中显示的单元格总数,而是一次在屏幕上显示的单元格数量。由于单元格尺寸非常小 (22x22),因此您一次可以在屏幕上看到 154 个单元格。渲染其中的每一个都会减慢您的界面速度。您可以通过增加 Storyboard 中的单元格大小并重新运行应用程序来证明这一点。

不幸的是,您无能为力。我建议通过避免裁剪到边界并尝试不实现来缓解问题drawRect:,因为它很慢。

于 2013-05-06T13:30:56.980 回答
6

对上面的两个答案竖起大拇指!这是您可以尝试的另一件事:UICollectionView通过禁用自动布局,我的性能有了很大的改进。虽然您必须添加一些额外的代码来布局单元内部,但自定义代码似乎比自动布局快得多。

于 2013-05-08T11:00:10.220 回答
4

在少数情况下,这是由于 UICollectionViewCell 中的自动布局。把它关掉(如果你可以没有它的话),滚动会变得很顺畅:) 这是一个 iOS 问题,他们已经很久没有解决了。

于 2015-04-10T11:45:20.977 回答
4

除了列出的答案(光栅化、自动布局等)之外,您可能还需要检查可能会降低性能的其他原因。

就我而言,我的每个 UICollectionViewCell 都包含另一个 UICollectionView (每个大约有 25 个单元格)。当每个单元格加载时,我调用内部 UICollectionView.reloadData(),这会显着降低性能。

然后我将 reloadData 放在主 UI 队列中,问题就消失了:

DispatchQueue.main.async {
    self.innerCollectionView.reloadData()
}

仔细研究这些原因可能也会有所帮助。

于 2016-10-18T05:13:46.990 回答
3

如果您正在实现网格布局,您可以通过使用单个UICollectionViewCellfor eachrow并将嵌套UIView的 's 添加到cell. 我实际上继承UICollectionViewCellUICollectionReusableView覆盖了prepareForReuse删除所有子视图的方法。在collectionView:cellForItemAtIndexPath:我添加了所有subviews最初是单元格的单元格,将它们的框架设置x为原始实现中使用的坐标,但将其y坐标调整为在cell. 使用这种方法,我仍然可以使用一些细节,UICollectionView例如targetContentOffsetForProposedContentOffset:withScrollingVelocity:在单元格的顶部和左侧很好地对齐。我从获得4-6 FPS到平稳的60 FPS

于 2014-07-24T18:29:16.073 回答
0

以为我会很快给出我的解决方案,因为我遇到了一个非常相似的问题——基于图像的 UICollectionView。

在我工作的项目中,我通过网络获取图像,将其缓存在本地设备上,然后在滚动期间重新加载缓存的图像。

我的缺陷是我没有在后台线程中加载缓存的图像。

一旦我将[UIImage imageWithContentsOfFile:imageLocation];其放入后台线程(然后通过我的主线程将其应用到我的 imageView),我的 FPS 和滚动就会好很多。

如果你还没有尝试过,一定要试一试。

于 2017-01-24T10:30:45.537 回答