IOS研究成果——在autolayout 如何实现 Cell 的高度自适应
2015-07-24 01:33
621 查看
Hello,小编又来了,距离上次写博客都有一段日子了,主要是这个月一直在赶新项目,项目推了又改改了又推,需求被改无数,泪奔。。。但是在做项目的时候发现了好多小细节,等项目做完了就拿来给大家看看,好了,不再多废话了,回归我们今天的主题。
其势 Autolayout 技术再 IOS6的时候就已经开始有了,但是大多数人还是习惯不了这个看起来奇特的东西,而且官方文档做到根本不是人看的,但是现在越来越多的人开始使用 autolayout 了,为什么?因为好用呀,就是这么简单,以前辛辛苦苦算 frame 值,现在大家就只需要抛开他,拥抱我们的 autolayout 就可以了,当然也有一些特殊情况,这里先不说,那么,我们在用 autolayout 的时候会碰到一个问题,怎么让我们的 cell 自适应呢?大家都知道,以前我们让 cell 自适应的时候,都是通过在模型赋值的时候计算出内容的 frame 值,也就是我们经常说的 frameModel,但是这个方法很沉郁,辛辛苦苦拿内容过去慢慢计算,稍微算法不好的同学就在这里跪了,那么 autolayout 完全就帮到你了,因为 label 的高度都是自适应的,非常好用,在说这个技术之前希望之前不太懂 autolayout 的同学恶补一下。。。。毕竟时代在进步,技术不断更新,在这里推荐大家使用一个第三方进行更好的运用 autolayout,到 GitHub 搜索Masonry这个绝对的代码 autolayout 布局的神器。当 你布局发生问题时还会有很好的提醒让你发现的那里出问题了。点击这里
先说一下思想:
1.启用估计行高(IOS8)下的实现,当你开启此方法再要 autolayout 布局的时候,自适应已经开启了,但是这只是使用与 IOS8以上的系统,当运行到 IOS7的时候,就会崩溃了
这样做是为了让tableView 上显示的 cell 临时的估计一个行高,主要使用来修改 tableView 右侧滚动条的大小,告诉它我下面还有多少内容,你自己看着办。但是一旦当 cell 出现在屏幕的时候这个值就会重新被计算,所以有时候我们设置的估算只比较少的时候,会出现一个滚动条跳跃的情况,这个会出现在估算一个 cell 的高度跟实际 cell 的高度相差比较大的时候出现,这个时候,你才应当实现tableView:estimatedHeightForRowAtIndexPath:方法,为每一行返回一个更精确的估算值。
IOS7下支持的方法(需要自己实现 cell 尺寸的自适应功能)
2.1创建一个用作缓存的实例,cacheCells
当然你要为在这个 tableView 显示的每一种 cell 都必须给一个重用的标识符,因为每一种cell 的布局不同,被估算出来的高度也会不相同的,这些 cell 完全是用作高度计算的,绝对不会被用作tableView:cellForRowAtIndexPath:方法的返回值以实际呈现在屏幕上。)接着,这个cell的内容(例如,文本、图片等等)还必须和会被显示在table view中的内容完全一致。
2.2使用估算行高
当你一加载程序进来的时候,你的 tableView 中已经有几十行 Cell,你会发现自动布局约束的解决方式在第一次加载table view的时候会迅速地卡住主线程。这是因为tableView在第一次加载中会调用 tableView:heightForRowAtIndexPath:方法(主要就是为了计算滚动条的大小)
所以在 IOS7中,你绝对可以使用estimatedRowHeight,让滚动条拥有一个估算的值,让不在屏幕中的 cell 告诉滚动条,我应该有多高,滚动条就会根据估算值而显示大小,然而,这些 cell 都会在显示在屏幕的时候计算出真实高度,那么滚动条也跟着改变。
上面也说过,如果使用估算值的话,当 cell 的高度与被估算的高度严重不匹配的时候,滚动条会有跳跃现象,这个时候,你才应当实现tableView:estimatedHeightForRowAtIndexPath:方法,为每一行返回一个更精确的估算值。
缓存行高(如果需要)
如果上面提到的你都做了,但是tableView:heightForRowAtIndexPath:的性能仍然慢的不可接受。非常不幸,你需要给行高做一些缓存(这是苹果的工程师们给出的改进建议)。大体的思路是,第一次计算时让自动布局引擎解析约束条件,然后将计算出的行高缓存起来,以后所有对该cell的高度的请求都返回缓存值。当然,关键还要确保任何会导致cell高度变化的情况发生时你都清除了缓存的行高——这通常发生在cell的内容变化时或其他重大事件发生时(比如用户调节了动态类型文本大小(Dynamic Type text size)的滑动条),当然,这是比较底层的做法,目前以小编的功力还没法做到这一点,但是 GitHub 上有一些第三方已经做出来了,等有空小编再发出来跟大家探讨一下
以下是 IOS7下实现 Cell 自适应高度的代码
那么在 cell 的内部就是这么实现的
其势 Autolayout 技术再 IOS6的时候就已经开始有了,但是大多数人还是习惯不了这个看起来奇特的东西,而且官方文档做到根本不是人看的,但是现在越来越多的人开始使用 autolayout 了,为什么?因为好用呀,就是这么简单,以前辛辛苦苦算 frame 值,现在大家就只需要抛开他,拥抱我们的 autolayout 就可以了,当然也有一些特殊情况,这里先不说,那么,我们在用 autolayout 的时候会碰到一个问题,怎么让我们的 cell 自适应呢?大家都知道,以前我们让 cell 自适应的时候,都是通过在模型赋值的时候计算出内容的 frame 值,也就是我们经常说的 frameModel,但是这个方法很沉郁,辛辛苦苦拿内容过去慢慢计算,稍微算法不好的同学就在这里跪了,那么 autolayout 完全就帮到你了,因为 label 的高度都是自适应的,非常好用,在说这个技术之前希望之前不太懂 autolayout 的同学恶补一下。。。。毕竟时代在进步,技术不断更新,在这里推荐大家使用一个第三方进行更好的运用 autolayout,到 GitHub 搜索Masonry这个绝对的代码 autolayout 布局的神器。当 你布局发生问题时还会有很好的提醒让你发现的那里出问题了。点击这里
先说一下思想:
1.启用估计行高(IOS8)下的实现,当你开启此方法再要 autolayout 布局的时候,自适应已经开启了,但是这只是使用与 IOS8以上的系统,当运行到 IOS7的时候,就会崩溃了
self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 44.0; // 设置为一个接近“平均”行高的值,用来估算修正tableView滚动条的高度
这样做是为了让tableView 上显示的 cell 临时的估计一个行高,主要使用来修改 tableView 右侧滚动条的大小,告诉它我下面还有多少内容,你自己看着办。但是一旦当 cell 出现在屏幕的时候这个值就会重新被计算,所以有时候我们设置的估算只比较少的时候,会出现一个滚动条跳跃的情况,这个会出现在估算一个 cell 的高度跟实际 cell 的高度相差比较大的时候出现,这个时候,你才应当实现tableView:estimatedHeightForRowAtIndexPath:方法,为每一行返回一个更精确的估算值。
IOS7下支持的方法(需要自己实现 cell 尺寸的自适应功能)
2.1创建一个用作缓存的实例,cacheCells
当然你要为在这个 tableView 显示的每一种 cell 都必须给一个重用的标识符,因为每一种cell 的布局不同,被估算出来的高度也会不相同的,这些 cell 完全是用作高度计算的,绝对不会被用作tableView:cellForRowAtIndexPath:方法的返回值以实际呈现在屏幕上。)接着,这个cell的内容(例如,文本、图片等等)还必须和会被显示在table view中的内容完全一致。
2.2使用估算行高
当你一加载程序进来的时候,你的 tableView 中已经有几十行 Cell,你会发现自动布局约束的解决方式在第一次加载table view的时候会迅速地卡住主线程。这是因为tableView在第一次加载中会调用 tableView:heightForRowAtIndexPath:方法(主要就是为了计算滚动条的大小)
所以在 IOS7中,你绝对可以使用estimatedRowHeight,让滚动条拥有一个估算的值,让不在屏幕中的 cell 告诉滚动条,我应该有多高,滚动条就会根据估算值而显示大小,然而,这些 cell 都会在显示在屏幕的时候计算出真实高度,那么滚动条也跟着改变。
上面也说过,如果使用估算值的话,当 cell 的高度与被估算的高度严重不匹配的时候,滚动条会有跳跃现象,这个时候,你才应当实现tableView:estimatedHeightForRowAtIndexPath:方法,为每一行返回一个更精确的估算值。
缓存行高(如果需要)
如果上面提到的你都做了,但是tableView:heightForRowAtIndexPath:的性能仍然慢的不可接受。非常不幸,你需要给行高做一些缓存(这是苹果的工程师们给出的改进建议)。大体的思路是,第一次计算时让自动布局引擎解析约束条件,然后将计算出的行高缓存起来,以后所有对该cell的高度的请求都返回缓存值。当然,关键还要确保任何会导致cell高度变化的情况发生时你都清除了缓存的行高——这通常发生在cell的内容变化时或其他重大事件发生时(比如用户调节了动态类型文本大小(Dynamic Type text size)的滑动条),当然,这是比较底层的做法,目前以小编的功力还没法做到这一点,但是 GitHub 上有一些第三方已经做出来了,等有空小编再发出来跟大家探讨一下
以下是 IOS7下实现 Cell 自适应高度的代码
static NSString *ID = @"Cell"; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 取出重用标示符对应的cell。 // 注意,如果重用池(reuse pool)里面没有可用的cell,这个方法会初始化并返回一个全新的cell, // 因此不管怎样,此行代码过后,你会可以得到一个布局约束已经完全准备好,可以直接使用的cell。 FSCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (!cell) { cell = [[FSCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; } // 这里先做比较简单的赋值一个 text 的内容,然后改写 cell的 text 属性的 set 方法,根据内容设计 label 的大小 NSString *text = self.dataSource[indexPath.row]; cell.text = text; [cell setNeedsUpdateConstraints]; [cell updateConstraintsIfNeeded]; return cell; }
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //在开发中可能会碰到不同的 cell 布局,那么我们就需要的设置不同的重用标识符reuseIdentifier,我们这里设置每种重用的 cell ,做一个缓存 // NSString *reuseIdentifier = [self.offscreenCells // 从cell字典中取出重用标示符对应的cell。如果没有,就创建一个新的然后存储在字典里面。 // 警告:不要调用table view的dequeueReusableCellWithIdentifier:方法,因为这会导致cell被创建了但是又未曾被tableView:cellForRowAtIndexPath:方法返回,会造成内存泄露! // 个人理解:一开始程序近来的时候是先走heightForRowAtIndexPath:方法,然后再走cellForRowAtIndexPath:方法,如果此时用了table view的dequeueReusableCellWithIdentifier:方法,但是 cell 并没有存在与缓存中,此时创建 cell 后,cell tableView:cellForRowAtIndexPath:并没有在被使用到的时候会造成内存泄露 FSCell *cell = [self.offscreenCells objectForKey:ID]; if (!cell) { cell = [[FSCell alloc]init]; [self.offscreenCells setObject:cell forKey:ID]; } // 赋值内容 NSString *text = self.dataSource[indexPath.row]; cell.text = text; // 确保cell的布局约束被设置好了,因为它可能刚刚才被创建好。 // 使用下面两行代码,前提是假设你已经在cell的updateConstraints方法中设置好了约束: [cell setNeedsUpdateConstraints]; [cell updateConstraints]; // 将cell的宽度设置为和tableView的宽度一样宽。 // 这点很重要。 // 如果cell的高度取决于table view的宽度(例如,多行的UILabel通过单词换行等方式), // 那么这使得对于不同宽度的table view,我们都可以基于其宽度而得到cell的正确高度。 // 但是,我们不需要在-[tableView:cellForRowAtIndexPath]方法中做相同的处理, // 因为,cell被用到table view中时,这是自动完成的。 // 也要注意,一些情况下,cell的最终宽度可能不等于table view的宽度。 // 例如当table view的右边显示了section index的时候,必须要减去这个宽度。 cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds)); // 触发cell的布局过程,会基于布局约束计算所有视图的frame。 // (注意,你必须要在cell的-[layoutSubviews]方法中给多行的UILabel设置好preferredMaxLayoutWidth值; [cell setNeedsLayout]; [cell layoutIfNeeded]; // 得到cell的contentView需要的真实高度 CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; // 要为cell的分割线加上额外的1pt高度。因为分隔线是被加在cell底边和contentView底边之间的。 height += 1.0f; NSLog(@"%p",cell); return height; }
// 注意:除非行高极端变化并且你已经明显的觉察到了滚动时滚动条的“跳跃”现象,你才需要实现此方法;否则,直接用tableView的estimatedRowHeight属性即可。 - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { // 以必需的最小计算量,返回一个实际高度数量级之内的估算行高。 // 例如: // if ([self isTallCellAtIndexPath:indexPath]) { return 250.0f; } else { return 40.0f; } }
那么在 cell 的内部就是这么实现的
#import <UIKit/UIKit.h> @interface FSCell : UITableViewCell @property (nonatomic,copy) NSString *text; @end
- (void)setText:(NSString *)text { _text = text; [self.contentView addSubview:self.Label]; self.Label.text = text; self.Label.font = [UIFont systemFontOfSize:20]; }
- (void)layoutSubviews { [super layoutSubviews]; // 必须设置好文字最大展示的宽度,这是必须的,autolayout 才能帮你排好计算好高度 self.Label.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds) - 20; }
- (void)updateConstraints { [super updateConstraints]; // updateConstraints 这个方法可能不止被调用一次,因此我们要避免添加相同布局约束,所有我们加一个布局完成的判断,让布局只布一次,以确保不见添加重复约束 // 另外,我们需要更新一个布局也应该写到updateConstraints这个方法里面,但是要在判断语句之前,这样才能确保每次调用都能执行 if (self.didSetUpConstraints) return; [self.Label mas_makeConstraints:^(MASConstraintMaker *make) { make.right.top.left.bottom.mas_equalTo(UIEdgeInsetsMake(10, 10, 10, 10)); }]; self.didSetUpConstraints = YES; }
- (UILabel *)Label { if (!_Label) { _Label = [[UILabel alloc]init]; _Label.numberOfLines = 0; } return _Label; }
最后说一句,如果你觉得这篇博文很赞,请给我点个赞,如果有大神觉得小编有那里做的不对,或者那里需要优化,请指点出来,让大家一起进步,不为别的,只是为了让大家都能好好成长起来,么么哒
相关文章推荐
- iOS 用命令实现简单的打包过程
- iOS Sprite Kit教程之使用帮助文档以及调试程序
- iOS Sprite Kit教程之申请和下载证书
- 初涉iOS 通知机制
- iOS推送 (百度推送)
- iOS开发笔记(3)---- 3DES/MD5加解密
- iOS编程:学习篇(八)
- iOS开发笔记(4)---- 反射
- OS X 和 iOS 中的多线程技术
- 小白学开发(iOS)OC_类方法和对象方法(2015 b052 -07-22)
- 小白学开发(iOS)OC_类和对象(2015-07-22)
- IOS - 会员信息提示
- iOS 消息推送及本地通知,原理解析
- iOS 设备信息获取
- iOS 静态库制作及使用问题
- iOS中Block介绍(一)基础
- iOS 监听文本框的改变 代码片段
- iOS7新特性-NSURLSession详解
- IOS键盘收放以及通知
- iOS前期OC训练OC_07NSDate