UICollectionView性能 – _updateVisibleCellsNow

我正在处理自定义的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

时间档案: 时间档案 - 自定义布局

更新

也许它徒劳? 一些使用UICollectionViewController的普通UICollectionViewController进行一些testing,得到大致相同数量的单元格/屏幕,从而得到类似的时间configuration文件。

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

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

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

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

没有这个,我有10fps,55fps的热潮! 我不是很熟悉GPU和合成模型,所以它到底做了什么并不完全清楚,但基本上它只是在一个位图(而不是每个子视图中的一个位图)渲染所有子视图。 无论如何,我不知道它是否有一些缺点,但它显着更快!

我一直在做相当多的UICollectionView性能调查。 结论很简单。 大量电池的性能差。



编辑:道歉,只是重新读你的post,你有的细胞数量应该是确定(见我的评论的其余部分),所以细胞复杂性也可能是一个问题。

如果您的devise支持,请检查:

  1. 每个细胞都是不透明的。

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

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

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

  5. 尽量避免阴影。



其原因其实很简单。 许多人不明白这一点,但值得了解:UIScrollViews不使用Core Animation滚动。 我的天真的信念是,他们涉及一些秘密滚动animation“酱”,只是偶尔要求代表偶尔更新来检查状态。 但实际上,卷动视图是根本不直接应用任何绘图行为的对象。 他们真正的所在是一个应用抽象它们包含的UIViews的坐标位置的math函数的类,所以视图坐标被视为相对于抽象的contentView平面而不是相对于包含视图的原点。 滚动视图将根据用户input(例如刷卡)更新抽象滚动平面的位置,当然还有一个物理algorithm,它给出翻译后的坐标位置的“时间”。

现在,如果要生成自己的集合视图布局对象,理论上可以生成一个100%反转底层滚动视图所应用的math转换的集合视图布局对象。 这将是有趣的,但没用,因为它会出现,当你滑动细胞不会移动。 但是我提出了这种可能性,因为它说明了使用集合视图对象本身的集合视图布局对象本身对底层滚动视图执行了非常类似的操作。 例如,它只是提供了一个机会,通过对要显示的视图的属性进行帧转换来应用额外的math框架,并且主要地,这将是简单的位置属性的转换。

只有当插入或删除新的单元格被移动或重新加载时,CoreAnimation才会涉及; 通常通过呼吁:

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

UICollectionView为每个滚动框架请求单元格layoutAttributes,并为每个框架布置每个可见的视图。 UIView是丰富的对象,优于灵活性而不仅仅是性能。 每次布局时,系统都会执行一些testing来检查它的alpha,zIndex,subViews,裁剪属性等等。列表很长。 正在对每个帧的每个集合视图项进行这些检查和对视图的任何结果更改。

为了确保良好的性能,所有的逐帧操作都需要在17ms内完成。 [随着你有的细胞的数量,这是根本不会发生]括号这一条款,因为我已经重新读你的文章,我意识到我误解了它。 随着你有的单元数量,应该不会有性能问题。 我个人发现,只有一个caching图像的香草细胞的简化testing,在iPad 3上testing的限制大约是784个屏幕单元,然后性能开始下降到50fps以下。

在实践中,你将需要保持低于这个。

我个人使用我自己的自定义布局对象,需要比UICollectionView提供更高的性能。 不幸的是,我没有运行上面的简单testing,直到开发path,我意识到有性能问题。 我是这样,我要重新开放UICollectionView,PSTCollectionView的开源后端。 我认为有一个解决方法,可以实现,所以只是为了一般的滚动,每个单元格项目的图层是使用绕过每个UIView单元格布局的操作写入的。 这将对我有用,因为我有我自己的布局对象,我知道什么时候需要布局,我有一个巧妙的把戏,这将允许PSTCollectionView回到正常的操作模式在这个时候。 我已经检查了通话顺序,看起来不是太复杂,也不是完全不可行。 但可以肯定的是,这是不平凡的,还需要做一些进一步的testing,然后才能确认它会起作用。

还有一些可能有用的观察:

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

在这里输入图像说明

什么最终花费最多的时间似乎是与_updateVisibleCellsNow内的一个可变的NSDictionary工作。 既复制字典,又按键查找项目。 键似乎是UICollectionViewItemKey和[UICollectionViewItemKey isEqual:]方法是所有的最耗时的方法。 UICollectionViewItemKey至less包含typeidentifierindexPath属性,包含的属性indexPath comparison [NSIndexPath isEqual:]占用的时间最多。

从这我猜猜UICollectionViewItemKey的哈希函数可能是缺乏的,因为isEqual:在字典查找过程中经常调用。 许多项目可能会以相同的散列结束(或在相同的散列桶中,不知道NSDictionary是如何工作的)。

由于某些原因,1部分中的所有项目的速度都会更快,而相比之下,每个部分中的7个项目的许多部分相比, 可能是因为它在NSIndexPath isEqual中花费了这么多时间,而且如果该行最先差异,或者UICollectionViewItemKey可能会获得更好的散列,则速度会更快。

老实说,UICollectionView确实让那些重字典工作在每一个滚动框架上感觉很奇怪,正如之前提到的,为了避免滞后,每个框架更新需要<16ms。 我想知道这么多字典查找是否是:

  • 对于一般的UICollectionView操作来说确实是必需的
  • 在那里支持一些很less使用的边缘情况,可以closures大多数布局
  • 尚未修复的一些未优化的内部代码
  • 我们的一些错误

希望WWDC在今年夏天能够看到一些改进,或者如果其他人能够找出如何解决的话。

问题不在于您在收集视图中显示的单元总数,而是一次显示在屏幕上的单元的数量。 由于单元格大小非常小(22×22),因此一次可以在屏幕上显示154个单元格。 渲染这些都是减慢你的界面。 您可以通过增加Storyboard中的单元格大小并重新运行应用程序来certificate这一点。

不幸的是,你可以做的事情不多。 我build议通过避免削减到边界,并试图不执行drawRect:缓解这个问题,因为它很慢。

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

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

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

另外,应该注意的是,这段代码进入cellForItemAtIndexPath的collectionView委托方法。

还有一个技巧 – 看到一个应用程序的每秒帧数(FPS),打开仪器下的核心animation(见图)。

在这里输入图像说明

就像@Altimac,我也用这个代码解决了在UICollectionView上滚动的问题:

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

我也有6-10fps,现在我有57fps

在less数情况下,这是由于UICollectionViewCell中的自动布局。 把它关掉(如果你可以没有它),滚动将成为黄油顺利:)这是一个iOS问题,他们havnt从年龄解决。

如果你正在实现一个网格布局,你可以通过为每一row使用一个单一的UICollectionViewCell来解决这个问题,并将嵌套的UIView添加到cell 。 我实际上是UICollectionViewCellUICollectionReusableView subclassed和UICollectionReusableView prepareForReuse方法删除所有的子视图。 在collectionView:cellForItemAtIndexPath:我添加了所有的subviews ,这些subviews最初是把它们的框架设置为原始实现中使用的x坐标的cell ,但将它的y坐标调整为在cell 。 使用这种方法,我仍然可以使用UICollectionView某些细节,如targetContentOffsetForProposedContentOffset:withScrollingVelocity:在单元格的顶部和左侧很好地alignment。 我从4-6 FPS变成了平滑的60 FPS

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

在我的情况下,每个UICollectionViewCell都包含另一个UICollectionView(每个约25个单元格),当每个单元格加载时,我调用内部的UICollectionView.reloadData(),这会显着拖慢性能。

然后我把reloadData放在主UI队列中,问题就没有了:

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

仔细研究这些原因也可能有帮助。 谢谢! 🙂

以为我会很快给我的解决scheme,因为我面临一个非常类似的问题 – 基于图像的UICollectionView。

在我工作的项目中,我通过networking获取图像,在设备上本地caching图像,然后在滚动期间重新加载caching的图像。

我的缺点是我没有加载caching的图像在后台线程。

一旦我把我的[UIImage imageWithContentsOfFile:imageLocation]; 到后台线程(然后通过我的主线程应用到我的imageView),我的FPS和滚动是一个更好的。

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