如何在iOS中快速流畅地进行表格滚动

表格视图是否以60 fps(每秒帧数)滚动? 还是看起来生涩而不光滑? 我的现在以60 fps的速度平稳运行。 它曾经不稳定,平均速度约为38 fps。 这就是我做的。

每行中具有复杂单元格的表可能难以平滑滚动。 您需要在滚动时配置和布局单元,这是有限的时间。 当需要看时,滚动会变得缓慢而生涩。 我的平均速度约为38 fps(尽管分布范围很广)。 显然很慢。 问题在于表单元格是一个自定义视图,其中包含其他自定义视图,并且这些视图的位置和大小受到各种限制。 看起来非常不错,这是一个很棒的UI。 但是,当我们的表视图控制器代表被要求提供行高时,很难很快地弄清这一点。

问题

在我们的应用程序中,表格单元格的大小可能会因数据而异,几乎相差2倍! 因此,我们在调用heightForRowAtIndexPath方法时配置并布局一次单元,然后在调用cellForRowAtIndexPath方法时再次进行所有操作。 我们本可以使用estimatedHeightForRowAtIndexPath方法。 但是对我们来说,如果不进行完整的布局很难估算。

另一位开发人员希望表格准确无误。 因此,他们只是设置单元格视图并在其上进行布局,然后询问该单元格视图的高度。 那行得通,而且是准确的。 但是非常慢。

为什么我要关心的是38 fps,而不是60? 应用程序的响应性在快速动作的工作方式(如滚动等快速动作)中非常明显。 如果滚动是平滑的,则用户可以快速流畅地查看应用程序。 而且以平均38 fps的速度不规律地滚动时,会显得生涩且不流畅。 60 fps的速度与屏幕更新的速度一样快,因此必须每16毫秒更新一次(实际上由于系统和图形开销而要少)。

我在委托中的heightForRowAtIndexPathcellForRowAtIndexPath方法上设置了一个断点,并看到多次调用该断点。 这段代码每次都在重做单元格布局。 有趣的是,我看到在heightForRowAtIndexPath.之前叫做cellForRowAtIndexPath heightForRowAtIndexPath. 与我预期的相反,但这有所帮助。

第一次尝试

我试图简化该过程,并根据行的元素和潜在的配置来计算高度。 这演变成一系列让我不安的特殊情况。 这很复杂,无法支持。 这意味着更改单元格不仅仅是更改xib文件的问题,而是再次对布局计算重新进行反向工程以估计或计算高度。

我不喜欢为要维护代码的人添加地雷。 (可能是我!)这行不通。

真正的解决方案:

因此,我回到了“布局并询问高度”方法。 但是这一次,我将在单元格上进行布局时缓存高度。

我创建了一个像这样的类:

@interface MyCachedRowHeight : NSObject
@property (nonatomic, strong) NSIndexPath *path;
@property (nonatomic,) CGFloat height;
@end

然后在我的表委托中,我创建了一个NSMutableArray 。 当cellForRowAtIndexPathheightForRowAtIndexPath必须配置和布局单元格时,我将使用数组中的上述类缓存单元格的高度和路径。 在heightForRowAtIndexPath我将首先检查高速缓存中是否存在该路径。 如果找到,它将返回该高度。

我还编写了一些方法,不仅可以缓存高度并检查缓存,还可以从缓存中删除一行或完全删除缓存内容。 这些是为了允许对给定行进行配置更改或编辑,或者允许从服务器重新获取整个表的内容。

该缓存使我达到了60 fps的目标,并使表格中的滚动变得轻松便捷。

我本可以做的

在我的情况下,每个单元只能进行一种布局。 但是,如果这不能让我达到60 fps,该怎么办? 在我的缓存中,我没有尝试优化行顺序或智能搜索。 这是一个线性搜索。 由于此高速缓存大小中的项目总数不到几百,最有可能在10到20之间,因此在这种情况下获得搜索或排序并不重要。 但是对于较大的缓存,良好的排序和智能搜索可能会有用。

原始实现中的实际成本是单元的多种配置和布局,布局特别昂贵。 由于一次调用了cellForRowAtIndexPath ,多次调用了heightForRowAtIndexPath ,并且每次调用都完成了布局,多次布局时间使我丧命。 如果布局的成本更高,并且仍然引起问题,那么我可以简单地缓存整个单元格视图。 我的方法可能会进行两次布局-一次用于高度,一次用于单元。

因为我达到了60 fps的目标,所以我不需要这样做。 这可能是因为我发现在heightForRowAtIndexPath之前调用了cellForRowAtIndexPath 。 此呼叫顺序意味着我的表格单元只需要一个布局。 但是对于更复杂的单元并上下滚动(从而重新布置单元),单元缓存可能是有用的。

我是如何追踪的?

我使用了Xcode中的工具。 我使用Xcode菜单“产品”>“配置文件”启动了Instruments。 最初,我仅使用时间分析来查看问题的总体所在。 然后,我使用了Core Animation工具,该工具既可以进行时间分析,又可以显示每秒的帧数。 我想要的是在屏幕上流畅的动画和运动,我知道这与fps有关,因此我使用了显示动画的工具。

故事的寓意:衡量您要改进的地方。 缓存所需的数据非常昂贵。