UITableView

UITableView 原理

  • UITableView是UIScrollView的子类,所以它能够自动响应滚动事件(通常为上下滚动)。
  • 它内部包含0到多个UITableViewCell对象,每一个table cell展现各自的内容。当新cell须要被显示时,就会调用tableView:cellForRowAtIndexPath:方法来获取或建立一个cell;而不可视时,它又会被释放。因而可知,同一时间其实只须要存在一屏幕的cell对象便可,不须要为每一行建立一个cell。
  • 此外,UITableView还能够分为多个sections,每一个区段均可以有本身的head、foot和cells。而在定位一个cell时,就须要2个字段了:在哪一个section,以及在这个section的第几行。这在iOS SDK中是用NSIndexPath来表述的,UIKit为其添加了indexPathForRow:inSection:这个建立方法。

UITableView 的简单的优化

  1. 使用不透明视图
    不透明的视图能够极大地提升渲染的速度。所以如非必要,能够将table cell及其子视图的opaque属性设为YES(默认值)。
    其中的特例包括背景色,它的alpha值应该为1(例如不要使用clearColor);图像的alpha值也应该为1,或者在画图时设为不透明。
  2. 不要重复建立没必要要的table cell
    前面说了,UITableView只须要一屏幕的UITableViewCell对象便可。所以在cell不可见时,能够将其缓存起来,而在须要时继续使用它便可。
    而UITableView也提供了这种机制,只须要简单地设置一个identifier便可:git

    1
    2
    3
    4
    5
    static NSString *CellIdentifier = @"xxx";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
  3. 不要作多余的绘制工做。
    在实现drawRect:的时候,它的rect参数就是须要绘制的区域,这个区域以外的不须要进行绘制。
    例如上例中,就能够用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否须要绘制image和text,而后再调用绘制方法。github

    优化UITableViewCell高度计算的那些事

    下面是转载于 BLOG: SUNNYXX – 优化UITableViewCell高度计算的那些事
    他们也在维护一个开源的扩展,UITableView+FDTemplateLayoutCell,让高度计算这个事情变的史无前例的简单,也受到了不少星星的支持,github连接请戳我缓存

UITableViewCell高度计算

rowHeightide

UITableView是咱们再熟悉不过的视图了,它的 delegate 和 data source 回调不知写了多少次,也难免遇到 UITableViewCell 高度计算的事。UITableView 询问 cell 高度有两种方式。
一种是针对全部 Cell 具备固定高度的状况,经过:工具

1
self.tableView.rowHeight = 88;

 

上面的代码指定了一个全部 cell 都是 88 高度的 UITableView,对于定高需求的表格,强烈建议使用这种(而非下面的)方式保证没必要要的高度计算和调用。rowHeight属性的默认值是 44,因此一个空的 UITableView 显示成那个样子。优化

另外一种方式就是实现 UITableViewDelegate 中的:ui

1
2
3
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 88;
}

须要注意的是,实现了这个方法后,rowHeight 的设置将无效。因此,这个方法适用于具备多种 cell 高度的 UITableView。spa

estimatedRowHeight
这个属性 iOS7 就出现了, 文档是这么描述它的做用的:设计

If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.code

恩,听上去蛮靠谱的。咱们知道,UITableView 是个 UIScrollView,就像平时使用 UIScrollView 同样,加载时指定 contentSize 后它才能根据本身的 bounds、contentInset、contentOffset 等属性共同决定是否能够滑动以及滚动条的长度。而 UITableView 在一开始并不知道本身会被填充多少内容,因而询问 data source 个数和建立 cell,同时询问 delegate 这些 cell 应该显示的高度,这就形成它在加载的时候浪费了多余的计算在屏幕外边的 cell 上。和上面的 rowHeight 很相似,设置这个估算高度有两种方法:

1
2
3
4
5
self.tableView.estimatedRowHeight = 88;
// or
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// return xxx
}

 

有所不一样的是,即便面对种类不一样的 cell,咱们依然可使用简单的 estimatedRowHeight 属性赋值,只要总体估算值接近就能够,好比大概有一半 cell 高度是 44, 一半 cell 高度是 88, 那就能够估算一个 66,基本符合预期。

  1. 设置估算高度后,contentSize.height 根据“cell估算值 x cell个数”计算,这就致使滚动条的大小处于不稳定的状态,contentSize 会随着滚动从估算高度慢慢替换成真实高度,肉眼可见滚动条忽然变化甚至“跳跃”。
  2. 如果有设计很差的下拉刷新或上拉加载控件,或是 KVO 了 contentSize 或 contentOffset 属性,有可能使表格滑动时跳动。
  3. 估算高度设计初衷是好的,让加载速度更快,那凭啥要去侵害滑动的流畅性呢,用户可能对进入页面时多零点几秒加载时间感受不大,可是滑动时实时计算高度带来的卡顿是明显能体验到的,我的以为还不如一开始都算好了呢(iOS8更过度,即便都算好了也会边划边计算)

UITableView+FDTemplateLayoutCell

使用UITableView+FDTemplateLayoutCell 无疑是解决算高问题的最佳实践之一,既有 iOS8 self-sizing 功能简单的 API,又能够达到 iOS7 流畅的滑动效果,还保持了最低支持 iOS6。
使用起来大概是这样:

<uitableview+fdtemplatelayoutcell.h>

1
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) {
        // 配置 cell 的数据源,和 "cellForRow" 干的事一致,好比:
        cell.entity = self.feedEntities[indexPath.row];
    }];
}

 

写完上面的代码后,你就已经使用到了:

  • 和每一个 UITableViewCell ReuseID 一一对应的 template layout cell
    这个 cell 只为了参加高度计算,不会真的显示到屏幕上;它经过 UITableView 的 -dequeueCellForReuseIdentifier: 方法 lazy 建立并保存,因此要求这个 ReuseID 必须已经被注册到了 UITableView 中,也就是说,要么是 Storyboard 中的原型 cell,要么就是使用了 UITableView 的 -registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的注册方法。
  • 根据 autolayout 约束自动计算高度
    使用了系统在 iOS6 就提供的 API:-systemLayoutSizeFittingSize:
  • 根据 index path 的一套高度缓存机制
    计算出的高度会自动进行缓存,因此滑动时每一个 cell 真正的高度计算只会发生一次,后面的高度询问都会命中缓存,减小了很是可观的多余计算。
  • 自动的缓存失效机制
    无须担忧你数据源的变化引发的缓存失效,当调用如-reloadData,-deleteRowsAtIndexPaths:withRowAnimation:等任何一个触发 UITableView 刷新机制的方法时,已有的高度缓存将以最小的代价执行失效。如删除一个 indexPath 为 [0:5] 的 cell 时,[0:0] ~ [0:4] 的高度缓存不受影响,而 [0:5] 后面全部的缓存值都向前移动一个位置。自动缓存失效机制对 UITableView 的 9 个公有 API 都进行了分别的处理,以保证没有一次多余的高度计算。
  • 预缓存机制
    预缓存机制将在 UITableView 没有滑动的空闲时刻执行,计算和缓存那些尚未显示到屏幕中的 cell,整个缓存过程彻底没有感知,这使得完整列表的高度计算既没有发生在加载时,又没有发生在滑动时,同时保证了加载速度和滑动流畅性,下文会着重讲下这块的实现原理。

开始使用UITableView+FDTemplateLayoutCell

若是你以为这个工具能帮获得你,整合到工程也十分简单。
使用 cocoapods:

1
pod search UITableView+FDTemplateLayoutCell

 

欢迎使用和支持这个工具,有 bug 请随时反馈哦~
再复习下 github 地址: https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

相关文章
相关标签/搜索