如何根据动态的cell数量改变table cellView的高度

这已经是开始做ios的第二个正式项目了,因为是从7以后才从java转做ios,所以喜欢storyboard以及autolayout多一点,几乎都用autolayout去绘制界面(因为简单,省时间嘛)。
那么first problem来了, 使用自定义表格时如何动态获取uitablecell的高度呢。
网上提供了很多方法,
  方法一:使用[self tableview :self.tableview cellForRowAtIndexPath:indexPath]获取该行的cell,然后获取return cell.frame.size.height。看起来十分简单,可惜本人试验失败,会在获取cell的方法上卡死。上网查资料发现,heightForRowAtIndexPath方法执行其实是在cellForRowAtIndexPath之前,没生成呢,去哪获取啊?所以法一不可取
  方法二:通俗易懂的解释就是计算法了,最常见的就是使用boundingRectWithSize方法,对text字符串 进行计算了。好吧,我得承认第一项目就是这么做的,一点一点的把label的条件补全(比如,行数,font,size等),再加上上下各个控件,以及控件间距的约束,高度也就算出来了。但是本人着实有点懒,很多问题,宁可花费大量时间去寻找简单解法以确保节省日后的工作量,也不愿意每次都勤勤恳恳的计算每个控件的高度,因为你想,这种常用的方法苹果没有理由给不出简单有效的方法不是?于是就上网一顿猛找呀,然后找到的看似十分可行的方法。即方法三
  方法三(推荐):使用自动布局动态调整cell高度
  如果对自动布局不太了的哥们,或者参考下这个链接,其实第三种方法的这个链接里兄台已经分析的很清楚了,只是照搬在自己的程序中依然会出现一些小问题&&即不是每次都能计算准确,有时会出现串行的现象,最后在一个老外那里寻到了解释,附上,英语好的同学可以自己去看看,这里就简单吧最后的解决芳芳给大家屡一下。
  1.需要使用自动布局,给cell中的每一个控件上下左右添加相应约束,尤其是上下方向,一定要有从上之下可以观察到一条线式约束存在。这里附上一张图片。(记得自定义字体以及最大行数等信息。)
  注意:尤其容易忽略的是最下方的空间距离super view的底部的距离,一定要加上!如果垂直方向上有多个控件,最上层和最下层控件要设置距离顶部和距离底部的约束,然后这些控件之间可以通过设置垂直方向上的距离来完成上下约束一条线。
  2.修改水平数值方向上的Content Hugging Priority 和Content Compression Resistance Priority值
  如图上类似的cell,设置title的值
  设置subtitle的值
  3.然后最重点的部分来了,heightForRowAtIndexPath中的方法如下:
1 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
int row = (int)indexPath.
NSDictionary * dic = [_datasource objectAtIndex:row];
//案件名称
NSString * titlesStr = [caseOnlineDic objectForKey:@"title"];
NSString * subTitleStr = [caseOnlineDic objectForKey:@"subTitle"];
//注意这里一定要使用静态,并且只使用一次的生成方式,以确保节省内存,以及内容准确不换行。
static MyCell *cell =
static dispatch_once_t onceT
dispatch_once(&onceToken, ^{
cell = [self.myTableView dequeueReusableCellWithIdentifier:identifier];
cell.title.text = titlesS
cell.subtitle.text = subTitleS
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].
阅读(...) 评论() &优化UITableViewCell高度计算的那些事 附源码
招聘信息:
我是前言这篇文章是我和我们团队最近对 UITableViewCell 利用 AutoLayout 自动高度计算和 UITableView 滑动优化的一个总结。我们也在维护一个开源的扩展,UITableView+FDTemplateLayoutCell,让高度计算这个事情变的前所未有的简单,也受到了很多星星的支持,这篇总结你可以读到:UITableView高度计算和估算的机制不同iOS系统在高度计算上的差异iOS8 self-sizing cellUITableView+FDTemplateLayoutCell如何用一句话解决高度问题UITableView+FDTemplateLayoutCell中对RunLoop的使用技巧UITableViewCell高度计算rowHeightUITableView是我们再熟悉不过的视图了,它的 delegate 和 data 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。estimatedRowHeight这个属性 iOS 7 就出现了, 文档是这么描述它的作用的: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;
-&(CGFloat)tableView:(UITableView&*)tableView&estimatedHeightForRowAtIndexPath:(NSIndexPath&*)indexPath&{
&&&&//&return&xxx
}有所不同的是,即使面对种类不同的 cell,我们依然可以使用简单的 estimatedRowHeight 属性赋值,只要整体估算值接近就可以,比如大概有一半 cell 高度是 44, 一半 cell 高度是 88, 那就可以估算一个 66,基本符合预期。说完了估算高度的基本使用,可以开始吐槽了:设置估算高度后,contentSize.height 根据“cell估算值 x cell个数”计算,这就导致滚动条的大小处于不稳定的状态,contentSize 会随着滚动从估算高度慢慢替换成真实高度,肉眼可见滚动条突然变化甚至“跳跃”。若是有设计不好的下拉刷新或上拉加载控件,或是 KVO 了 contentSize 或 contentOffset 属性,有可能使表格滑动时跳动。估算高度设计初衷是好的,让加载速度更快,那凭啥要去侵害滑动的流畅性呢,用户可能对进入页面时多零点几秒加载时间感觉不大,但是滑动时实时计算高度带来的卡顿是明显能体验到的,个人觉得还不如一开始都算好了呢(iOS8更过分,即使都算好了也会边划边计算)iOS 8 self-sizing cell具有动态高度内容的 cell 一直是个头疼的问题,比如聊天气泡的 cell, frame 布局时代通常是用数据内容反算高度:CGFloat&height&=&textHeightWithFont()&+&imageHeight&+&topMargin&+&bottomMargin&+&...;供 UITableViewDelegate 调用时很可能是个 cell 的类方法:@interface&BubbleCell&:&UITableViewCell
+&(CGFloat)heightWithEntity:(id)
@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 都可以享受到:这个特性首先要求是 iOS 8,要是最低支持的系统版本小于8的话,还得针对老版本单写套老式的算高(囧),不过用的 API 到不是新面孔:self.tableView.estimatedRowHeight&=&213;
self.tableView.rowHeight&=&UITableViewAutomaticD这里又不得不吐槽了,自动计算 rowHeight 跟 estimatedRowHeight 到底是有什么仇,如果不加上估算高度的设置,自动算高就失效了- -PS:iOS8 系统中 rowHeight 的默认值已经设置成了 UITableViewAutomaticDimension,所以第二行代码可以省略。问题:这个自动算高在 push 到下一个页面或者转屏时会出现高度特别诡异的情况,不过现在的版本修复了。求一个能让最低支持 iOS8 的公司- -iOS8抽风的算高机制相同的代码在 iOS7 和 iOS8 上滑动顺畅程度完全不同,iOS8 莫名奇妙的卡。很大一部分原因是 iOS8 上的算高机制大不相同,这是我做的小测试:研究后发现这么多次额外计算有下面的原因:不开启高度估算时,UITableView 上来就要对所有 cell 调用算高来确定 contentSizedequeueReusableCellWithIdentifier:forIndexPath: 相比不带 “forIndexPath” 的版本会多调用一次高度计算iOS 7 计算高度后有”缓存“机制,不会重复计算;而 iOS8 不论何时都会重新计算 cell 高度iOS 8 把高度计算搞成这个样子,从 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:^(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,整个缓存过程完全没有感知,这使得完整列表的高度计算既没有发生在加载时,又没有发生在滑动时,同时保证了加载速度和滑动流畅性,下文会着重讲下这块的实现原理。我们在设计这个工具的 API 时斟酌了非常长的时间,既要保证功能的强大,也要保证接口的精简,一行调用背后隐藏着很多功能。这一套缓存机制能对滑动起多大影响呢?除了肉眼能明显的感知到外,我还做了个小测试:一个有 54 个内容和高度不同 cell 的 table view,从头滑动到尾,再从尾滑动到头,iOS 8 系统下,iPhone6,使用 Time Profiler 监测算高函数所花费的时间:未使用缓存API、未使用估算,共花费 877 ms:使用缓存API、开启估算,共花费 77 ms:测试数据的精度先不管,从量级上就差了一个数量级,说实话自己也没想到差距有这么大- -同时,工具也顺手解决了-preferredMaxLayoutWidth的问题,在计算高度前向 contentView 加了一条和 table view 宽度相同的宽度约束,强行让 contentView 内部的控件知道了自己父 view 的宽度,再反算自己被外界约束的宽度,破除“鸡生蛋蛋生鸡”的问题,这里比较 tricky,就不展开说了。下面说说利用 RunLoop 预缓存的实现。利用RunLoop空闲时间执行预缓存任务FDTemplateLayoutCell 的高度预缓存是一个优化功能,它要求页面处于空闲状态时才执行计算,当用户正在滑动列表时显然不应该执行计算任务影响滑动体验。一般来说,这个功能要耦合 UITableView 的滑动状态才行,但这种实现十分不优雅且可能破坏外部的 delegate 结构,但好在我们还有RunLoop这个工具,了解它的运行机制后,可以用很简单的代码实现上面的功能。空闲RunLoopMode在曾经的 RunLoop 线下分享会()中介绍了 RunLoopMode 的概念。当用户正在滑动 UIScrollView 时,RunLoop 将切换到 UITrackingRunLoopMode 接受滑动手势和处理滑动事件(包括减速和弹簧效果),此时,其他 Mode (除 NSRunLoopCommonModes 这个组合 Mode)下的事件将全部暂停执行,来保证滑动事件的优先处理,这也是 iOS 滑动顺畅的重要原因。当 UI 没在滑动时,默认的 Mode 是 NSDefaultRunLoopMode(同 CF 中的 kCFRunLoopDefaultMode),同时也是 CF 中定义的 “空闲状态 Mode”。当用户啥也不点,此时也没有什么网络 IO 时,就是在这个 Mode 下。用RunLoopObserver找准时机注册 RunLoopObserver 可以观测当前 RunLoop 的运行状态,并在状态机切换时收到通知:RunLoop开始RunLoop即将处理TimerRunLoop即将处理SourceRunLoop即将进入休眠状态RunLoop即将从休眠状态被事件唤醒RunLoop退出因为“预缓存高度”的任务需要在最无感知的时刻进行,所以应该同时满足:RunLoop 处于“空闲”状态 Mode当这一次 RunLoop 迭代处理完成了所有事件,马上要休眠时使用 CF 的带 block 版本的注册函数可以让代码更简洁:CFRunLoopRef&runLoop&=&CFRunLoopGetCurrent();
CFStringRef&runLoopMode&=&kCFRunLoopDefaultM
CFRunLoopObserverRef&observer&=&CFRunLoopObserverCreateWithHandler
(kCFAllocatorDefault,&kCFRunLoopBeforeWaiting,&true,&0,&^(CFRunLoopObserverRef&observer,&CFRunLoopActivity&_)&{
&&&&//&TODO&here
CFRunLoopAddObserver(runLoop,&observer,&runLoopMode);在其中的 TODO 位置,就可以开始任务的收集和分发了,当然,不能忘记适时的移除这个 observer分解成多个RunLoop Source任务假设列表有 20 个 cell,加载后展示了前 5 个,那么开启估算后 table view 只计算了这 5 个的高度,此时剩下 15 个就是“预缓存”的任务,而我们并不希望这 15 个计算任务在同一个 RunLoop 迭代中同步执行,这样会卡顿 UI,所以应该把它们分别分解到 15 个 RunLoop 迭代中执行,这时就需要手动向 RunLoop 中添加 Source 任务(由应用发起和处理的是 Source 0 任务)Foundation 层没对 RunLoopSource 提供直接构建的 API,但是提供了一个间接的、既熟悉又陌生的 API:-&(void)performSelector:(SEL)aSelector
&&&&&&&&&&&&&&&onThread:(NSThread&*)thr&
&&&&&&&&&&&&&withObject:(id)arg&
&&&&&&&&&&waitUntilDone:(BOOL)wait&
&&&&&&&&&&&&&&&&&&modes:(NSArray&*)这个方法将创建一个 Source 0 任务,分发到指定线程的 RunLoop 中,在给定的 Mode 下执行,若指定的 RunLoop 处于休眠状态,则唤醒它处理事件,简单来说就是“睡你xx,起来嗨!”于是,我们用一个可变数组装载当前所有需要“预缓存”的 index path,每个 RunLoopObserver 回调时都把第一个任务拿出来分发:NSMutableArray&*mutableIndexPathsToBePrecached&=&self.fd_allIndexPathsToBePrecached.mutableC
CFRunLoopObserverRef&observer&=&CFRunLoopObserverCreateWithHandler
(kCFAllocatorDefault,&kCFRunLoopBeforeWaiting,&true,&0,&^(CFRunLoopObserverRef&observer,&CFRunLoopActivity&_)&{
&&&&if&(mutableIndexPathsToBePrecached.count&==&0)&{
&&&&&&&&CFRunLoopRemoveObserver(runLoop,&observer,&runLoopMode);
&&&&NSIndexPath&*indexPath&=&mutableIndexPathsToBePrecached.firstO
&&&&[mutableIndexPathsToBePrecached&removeObject:indexPath];
&&&&[self&performSelector:@selector(fd_precacheIndexPathIfNeeded:)
&&&&&&&&&&&&&&&&&onThread:[NSThread&mainThread]
&&&&&&&&&&&&&&&withObject:indexPath
&&&&&&&&&&&&waitUntilDone:NO
&&&&&&&&&&&&&&&&&&&&modes:@[NSDefaultRunLoopMode]];
});这样,每个任务都被分配到下个“空闲” RunLoop 迭代中执行,其间但凡有滑动事件开始,Mode 切换成 UITrackingRunLoopMode,所有的“预缓存”任务的分发和执行都会自动暂定,最大程度保证滑动流畅。开始使用UITableView+FDTemplateLayoutCell如果你觉得这个工具能帮得到你,整合到工程也十分简单。使用 cocoapods:pod&search&UITableView+FDTemplateLayoutCell写这篇文章时的最新版本为 1.2,去除了前一个版本的黑魔法,增加了预缓存功能。欢迎使用和支持这个工具,有 bug 请随时反馈哦~再复习下 github 地址:
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量5547点击量4046点击量3713点击量3657点击量3251点击量3243点击量3231点击量3195点击量3116
&2016 Chukong Technologies,Inc.
京公网安备89中国领先的IT技术网站
51CTO旗下网站
对tableView三种计算动态行高方法的分析
tableView是一个神奇的东西,可以这么说,就算是一个初学者如果能把tableView玩的很6,那编一般的iOS的需求都问题不大了。tableView是日常开发中用烂了的控件,但是关于tableView中的自定义cell的动态行高,还是有一些玄机的。
作者:来源:博客园| 10:19
tableView是一个神奇的东西,可以这么说,就算是一个初学者如果能把tableView玩的很6,那编一般的iOS的需求都问题不大了。tableView是日常开发中用烂了的控件,但是关于tableView中的自定义cell的动态行高,还是有一些玄机的。笔者本次主要是因为预估行高的方法的问题作为了一个契机顺带写了此文对几种动态行高方法的分析。
如果你不是在董铂然博客园看到本文,请点击查看原文。
现在常规的动态行高的计算方法还是用
[str&boundingRectWithSize:size&options:NSStringDrawingUsesLineFragmentOrigin&attributes:attrs&context:nil].size&
这其中需要先传入一个最大尺寸和一个属性字典,特殊的格式要求都写在属性字典中。
NSDictionary&*attrs&=&@{NSFontAttributeName&:&font};&
整个流程的基本思想大概就是:用一个字符串对象来调用此方法,中间需要传入一个属性字典来告知字体和样式,然后根据字符串长度的多少来算出应该给多大的frame。前面传进的size一般可以设置最大宽度。 此方法一般写成分类便于调用。
#import&&NSString+Size.h&&&@implementation&NSString&(Size)&&&&&+&(CGSize)sizeWithString:(NSString&*)str&andFount:(UIFont&*)font&andMaxSize:(CGSize)size&{&NSDictionary&*attrs&=&@{NSFontAttributeName&:&font};&return&[str&boundingRectWithSize:size&options:NSStringDrawingUsesLineFragmentOrigin&attributes:attrs&context:nil].&}&&&&&-&(CGSize)sizeWithFount:(UIFont&*)font&andMaxSize:(CGSize)&{&NSDictionary&*attrs&=&@{NSFontAttributeName&:&font};&return&[self&boundingRectWithSize:size&options:NSStringDrawingUsesLineFragmentOrigin&attributes:attrs&context:nil].&}&@end&
这些方法从字面上看也比较容易理解。
调用时的代码基本就是取到一个字符串,传入一个font和一个最大size,如下把宽设置成了270就是最大宽度为270高度往下顺延的话就把高度写成MAXFLOAT
NSString&*text&=&_message.&CGSize&textSize&=&[text&sizeWithFount:[UIFont&systemFontOfSize:14]&andMaxSize:CGSizeMake(270,&MAXFLOAT)];&
然后在frame中取到最下面一个空间的maxY,从而让每一个cell在set方法中就得到自己的行高 ,然后通过cell的类方法返回。
随着iOS8的自动布局和Interface builder越来越成熟,逐渐衍生出了一种先用storyboard或xib界面再算自定义行高的方法。
这种方法一般需要先搭建一个图形化界面。如下图大概搭一个比较复杂的cell。
首先可以清晰的看出,用IB搭建看上去很快就能搭建完毕,并且有的图片或是view的背景设置了之后能看出界面大概的感觉。这里需要注意的就是label设置约束的方法,普通控件一般都要设置四个约束才能固定位置,label和button只设置两个约束(只需要写固定位置的两条约束,不需要写自身宽高的约束)也不会报错,但是需要在editor中设置sizeToFit,这样可以根据字数自动给你分配一个控件的大小。
一般评论类的label肯定都是字数比较多的,这时2条约束就不够了需要再设置一个最大宽度的约束,如图1我设置的方法是,把评论label与左右边界的间隙给设定了,这个在IB中叫Leading(前)和Training(后),高的约束我们没有写如果字数超过了一行他就会自己往下顺延。 这么写相较于把宽度约束写死的好处是会自动根据屏幕适配不管屏幕多大都是左右空出若干像素。这样做也有局限性,就是假设给这个label设置一个背景色,如果字数就5个字背景色也会延伸到一整行。如果QQ聊天页面也这样做,不管是几个字都是一整行的聊天气泡那会很丑。于是有了一种少则背景也少,多也不超过最大宽度的做法,就是设置label的width的less than来设置最大宽度。这么做如果字数不足一行的话,约束也会自动缩到与label长度匹配。
如果这个页面用纯手码写,可想而知会非常麻烦。
用IB页面做自定义行高的计算方法也更简单。也就是里面模型的set方法正常写,给自己的UI控件赋值。然后在tableView的行高方法heightForRow中,先给cell的模型赋值,然后再使用一次
[cell&layoutIfNeeded];&
他会自动根据填进去数值来布局,然后我们直接在这个方法中返回最下面一个控件的bottom位置+若干间隙,以此来作为行高即可。
真正的布局其实也就是用了这一行代码,并且可以做到屏幕适配不用if判断各种frame。但这样写也有一些问题,首先就是这么写从结构上来看不合理。这个行高方法中不应该写这些赋值语句。官方还是其他大神说不合理的原因,应该是这个方法应该仅仅是用来算出行高并显示的,会调用多次,如果在这里赋值性能会很差。这么说有道理,把这里面的每行代码都看一遍,能看出性能较差的方法主要就是这两行:1.给cell里模型赋值
2.layoutIfNeed 。如果调用多次这个方法那这两行也会执行多次,所以这应该是不科学的。我实际的做法是在其中设置一个行高缓存字典,并且找一个肯定不会重复的标识来做key值。每一行cell计算行高前都先拿自己的id去行高缓存字典里取一下看有没有值,如果有则直接返回对应的value,如果没有再计算。这样可以使这性能比较差得两行代码只执行一次。达到优化效果。
MTFBNoReplyCell&*feedbackNoreplyCell&=&[MTFBNoReplyCell&cell];&&NSString&*thisId&=[NSString&stringWithFormat:@&%d&,&feedbackModel.feedbackid];&&&CGFloat&cacheHeight&=&[[self.cellHeightCache&valueForKey:thisId]&doubleValue];&if&(cacheHeight)&{&&return&cacheH&}&&feedbackNoreplyCell.feedbackDetailModel&=&feedbackM&[feedbackNoreplyCell&layoutIfNeeded];&&[self.cellHeightCache&setValue:@(feedbackNoreplyCell.replyBtn.bottom+16)&forKey:thisId];&&return&feedbackNoreplyCell.replyBtn.bottom+16;&
大概的思想如上所示。 如果这个tableView的数据不会随时改变较为固定的话,可以把取到的模型作为value以indexpath.row为key存一个缓存字典这样也能优化一些。行高方法里取过了,cellForRow就可以直接用了。
预估行高方法
这里我想重点说一下这个预估行高的方法estimatedHeightForRowAtIndexPath 。这个方法可能大部分人一说到这个,就说这个方法好啊,预估行高方法可以减少heightForRow的调用次数,使得性能达到优化。 孰不知实际运用中是存在着一定问题的。
就拿整个tableView来说 他是继承自scrowView的,scrowView能够滚动是因为它有contentSize。tableView在初次加载的时候也需要算出自己的contentSize(而且会算不止一次),也就是说需要调一下所有的行高方法然后自己内部给他累加一下算出整个contentSize。如果在行高方法里设置一个打印会看到方法会调用很多次。这时如果有一个预估方法return 100。那它就能很快算出总值了。就会减少行高方法的调用,在实际用到某一行时再调用。
但是可能会出现如下左图的问题。
问题的原因就是,一开始预估方法给每行预估了一个行高,然后后面实际加载的行高与预估的行高不合时,会出现cell上下的&窜动&给人卡卡的感觉。对此我的思想是,如果是动态的且cell的复杂度较高,行与行之间差距大的时候,就直接不要写预估行高方法了吧,让他自己算吧哪怕多调用几次,毕竟上面已经写过缓存行高字典了,性能姑且是可以hold住了,并且不会出现&窜动&情况。如右图所示。
但是如果是固定行高有一种或是三种不同的cell,行高分别是120,150,200。你在预估行高了写个return 150。遇到行高与预估不等时,却也不会出现&窜动&。我推测应该是estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现&窜动&的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可。
关于上面行高的新方法和旧方法的对比,我的总结是:首先新方法肯定性能上是比旧方法要差一些的。具体体现在两个方面,1是在IB页面开发的东西,程序一启动就会全部加载进内存由系统托管,以至于有的界面你已经把导航控制器的栈顶控制器给pop了,发现内存还没有下降。2是新方法和旧方法有一个本质的区别,旧方法是直接算,算你需要多大的尺寸就告诉你,新方法则是先强制布局然后看你占了多大的尺寸再告诉你,这两者一对比,新方法就是多了一个强制布局的过程,这肯定是会对性能造成一定影响的,那具体影响多少?关于滑动计算行高我还不知道有什么可以明确一个数据的对比,我只能用肉眼看屏幕的滑动来区分对比,我的感觉就是基本没差别,如果要说有的话新方法可能会非常轻微的卡顿,换而言之就是同一个页面,旧方法编完需要10小时,新方法编完需要3小时,但是新方法的性能略差于旧方法。就看你自己怎么衡量了。当然非常庞大的项目还是建议用旧方法,毕竟一点一点的&略差于&积累在一起就是很差了。
关于iOS8新的行高特性
首先是有了一个新的用法。写在viewdidload里
self.tableView.estimatedRowHeight&=&50.0f;&self.tableView.rowHeight&=&UITableViewAutomaticD&
这就没什么好说的了,苹果自己帮你把动态行高计算了,所有乱七八糟的都不用管了。 但是暂时说这些基本没用,因为现在还看不到哪个公司的项目不适配iOS7,就算出了iOS9感觉也不会让你直接适配iOS8的,iOS7还会存在相当长一段时间,毕竟以后新系统版本改变应该都不会有iOS6到7变化那么大了,除非啥时候苹果总设计师乔纳森伊夫下台了。
【编辑推荐】
【责任编辑: TEL:(010)】
大家都在看猜你喜欢
关注热点原创原创聚焦
24H热文一周话题本月最赞
讲师:225395人学习过
讲师:149284人学习过
讲师:132133人学习过
精选博文论坛热帖下载排行
Cisco 640-802
Cisco Certified Network Associate (CCNA)
Testinside CCNA 640-802 V14 最新题库与Testinside CCNA 640-802 Q&A 192
订阅51CTO邮刊

我要回帖

更多关于 ios tableviewcell 的文章

 

随机推荐