UITableView优化

1.UITableViewCell高度计算

1.rowHeight

UITableView是我们再熟悉不过的视图了,它的delegatedata source回调不知写了多少次,也不免遇到 UITableViewCell 高度计算的事。UITableView 询问 cell 高度有两种方式。

一种是针对所有 Cell 具有固定高度的情况,通过:

self.tableView.rowHeight =88;

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

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

- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {

// return xxx

}

需要注意的是,实现了这个方法后,rowHeight的设置将无效。所以,这个方法适用于具有多种 cell 高度的 UITableView。

2.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.

恩,听上去蛮靠谱的。我们知道,UITableView 是个 UIScrollView,就像平时使用 UIScrollView 一样,加载时指定contentSize后它才能根据自己的 bounds、contentInset、contentOffset 等属性共同决定是否可以滑动以及滚动条的长度。而 UITableView 在一开始并不知道自己会被填充多少内容,于是询问 data source 个数和创建 cell,同时询问 delegate 这些 cell 应该显示的高度,这就造成它在加载的时候浪费了多余的计算在屏幕外边的 cell 上。和上面的rowHeight很类似,设置这个估算高度有两种方法: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更过分,即使都算好了也会边划边计算)

iOS8 self-sizing cell

具有动态高度内容的 cell 一直是个头疼的问题,比如聊天气泡的 cell, frame 布局时代通常是用数据内容反算高度:

CGFloatheight = textHeightWithFont() + imageHeight + topMargin + bottomMargin + ...;

供 UITableViewDelegate 调用时很可能是个 cell 的类方法:

@interfaceBubbleCell:UITableViewCell

+ (CGFloat)heightWithEntity:(id)entity;

@end

各种魔法 margin 加上耦合了屏幕宽度。

AutoLayout 时代好了不少,提供了-systemLayoutSizeFittingSize:的 API,在 contentView 中设置约束后,就能计算出准确的值;缺点是计算速度肯定没有手算快,而且这是个实例方法,需要维护专门为计算高度而生的template layout cell,它还要求使用者对约束设置的比较熟练,要保证 contentView 内部上下左右所有方向都有约束支撑,设置不合理的话计算的高度就成了0。

这里还不得不提到一个 UILabel 的蛋疼问题,当 UILabel 行数大于0时,需要指定preferredMaxLayoutWidth后它才知道自己什么时候该折行。这是个“鸡生蛋蛋生鸡”的问题,因为 UILabel 需要知道 superview 的宽度才能折行,而 superview 的宽度还依仗着子 view 宽度的累加才能确定。这个问题好像到 iOS8 才能够自动解决(不过我们找到了解决方案)

回到正题,iOS8 WWDC 中推出了self-sizing cell的概念,旨在让 cell 自己负责自己的高度计算,使用 frame layout 和 auto layout 都可以享受到:

这个特性首先要求是 iOS8,要是最低支持的系统版本小于8的话,还得针对老版本单写套老式的算高(囧),不过用的 API 到不是新面孔:

self.tableView.estimatedRowHeight =213;

self.tableView.rowHeight =UITableViewAutomaticDimension;

这里又不得不吐槽了,自动计算 rowHeight 跟 estimatedRowHeight 到底是有什么仇,如果不加上估算高度的设置,自动算高就失效了- -

PS:iOS8 系统中 rowHeight 的默认值已经设置成了 UITableViewAutomaticDimension,所以第二行代码可以省略。

iOS8抽风的算高机制

相同的代码在 iOS7 和 iOS8 上滑动顺畅程度完全不同,iOS8 莫名奇妙的卡。很大一部分原因是 iOS8 上的算高机制大不相同,这是我做的小测试:


研究后发现这么多次额外计算有下面的原因:

不开启高度估算时,UITableView 上来就要对所有 cell 调用算高来确定 contentSize

dequeueReusableCellWithIdentifier:forIndexPath:相比不带 “forIndexPath” 的版本会多调用一次高度计算

iOS7 计算高度后有”缓存“机制,不会重复计算;而 iOS8 不论何时都会重新计算 cell 高度

iOS8 把高度计算搞成这个样子,从 WWDC 也倒是能找到点解释,cell 被认为随时都可能改变高度(如从设置中调整动态字体大小),所以每次滑动出来后都要重新计算高度。

说了这么多,究竟有没有既能省去算高烦恼,又能保证顺畅的滑动,还能支持 iOS6+ 的一站式解决方案呢?

UITableView+FDTemplateLayoutCell

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

使用起来大概是这样:

#import

- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {

return[tableView fd_heightForCellWithIdentifier:@"identifer"cacheByIndexPath:indexPath configuration:^(idcell) {

// 配置 cell 的数据源,和 "cellForRow" 干的事一致,比如:

cell.entity =self.feedEntities[indexPath.row];

}];

}

开始使用UITableView+FDTemplateLayoutCell

如果你觉得这个工具能帮得到你,整合到工程也十分简单。

使用 cocoapods:

pod search UITableView+FDTemplateLayoutCell

写这篇文章时的最新版本为 1.2,去除了前一个版本的黑魔法,增加了预缓存功能。

欢迎使用和支持这个工具,有 bug 请随时反馈哦~

再复习下 github 地址:https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容