UITableView 是在app界面里很是经常使用的一个控件了,打开一个app,内容列表 做者列表 朋友圈列表等等,,,都离不开 UITableView 。 而 UITableView 的精髓,则是在 UITableViewCell 展示的, 最经常使用的 自定义cell 有的行高是固定的,而大部分 则须要根据内容来计算行高展现的。数组
我在写tableview时,基本都是自定义cell,而全部的自定义cell,都会继承一个基类BaseTableViewCell:缓存
.h里:
// 重用标识
+ (NSString *)reuseIdentifier;
// cell高度
+ (CGFloat)staticHeight;
.m里:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.opaque = NO;
self.selectionStyle = UITableViewCellSelectionStyleNone;
}
return self;
}
// 重用标识
+ (NSString *)reuseIdentifier {
return NSStringFromClass([self class]);
}
// cell高度
+ (CGFloat)staticHeight {
return 44.f;
}
复制代码
这样写的好处是,当咱们在使用tableview时,会方便咱们对重用标识符 行高使用,看一下: bash
staticHeight能够在子类的自定义cell里更改设置,使用时: app
这样写,更能清晰明了的看到对每一个自定义cell的设置,也会让代码看上去优雅整齐一些。less
实际开发中,使用最多的应该是动态计算cell高度了,这也是tableView很基本的一个功能。 好比搜索资讯这块: 布局
标题高度不固定,内容高度不固定,标签不固定, 这样的就须要根据model里的内容计算行高了:性能
用的时候,在tableview的代理里设置:优化
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
WMSearchResultQAModel *model = self.dataArray[indexPath.row];
return [WMSearchResultQAModel calutWholeCellHeightWithModel:model];
}
复制代码
这样就能够达到每一个cell根据内容展现不一样高度的要求了。 这种方法很繁琐,可是也是最精确的,最可控的,都支持autolayout和frame。ui
为何要缓存高度? 由于当tableView滚动时会不停的回调 heightForRowAtIndexPath 这个代理方法,当cell的高度需自适应内容时,就意味着每次回调这个方法时都要计算高度,而计算是要花时间了,在用户体验上的体现就是卡顿,众所周知 60fps是比较符合人眼审视的,若是帧数 低于这个数值过多,就会明显感觉到卡帧等现象,为了让用户体验比较好些,咱们就要对高度计算进行优化。lua
思路:为了不重复且无心义的计算cell高度,缓存高度就显得尤其重要了。
缓存高度机制
缓存高度 咱们须要一个容器来保存高度数值,能够是model 能够是一个可变数组 也能够是一个可变字典,以达到每当回调 heightForRowAtIndexPath 这个方法时,咱们先去这个缓存里去取,若是有,就直接拿出来,若是没有,就计算高度,而且缓存起来。
以model为例: 在model里声明个cellHeight属性,用于保存Model所对应的Cell的高度,而后在 heightForRowAtIndexPath 方法中,若是当前Model的cellHeight为0,说明这个Cell没有缓存太高度,则计算Cell的高度,并把这个高度记录在Model中,这样下次再获取这个Cell的高度,就能够直接去Model中获取,而不用从新计算:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
WMSearchResultQAModel *model = self.dataArray[indexPath.row];
if (model.cellHeight > 0) {
// 有缓存的高度,取出缓存高度
return model.cellHeight;
}
// 没有缓存时,计算高度并缓存起来
CGFloat cellHeight; = [WMSearchResultQAModel calutWholeCellHeightWithModel:model];
// 缓存给model
model.cellHeight = cellHeight;
return cellHeight;
}
复制代码
这样就实现了高度缓存和Model、Cell都对应的优化,咱们无需手动管理高度缓存,在添加和删除数据的时候,都是对Model在数据源中进行添加或删除。 而若是使用可变数组或可变字典时,则须要额外的在刷新tableView时对其进行清空处理。
在 iOS8 以后,系统结合autolayout提供了动态结算行高的方法 UITableViewAutomaticDimension,作好约束,咱们都不用去实现 heightForRowAtIndexPath 这个代理方法了。
masonry支持毫无压力。
实现步骤:
// 预设行高
self.tableView.estimatedRowHeight = xxx;
// 自动计算行高模式
self.tableView.rowHeight = UITableViewAutomaticDimension;
复制代码
- (void)layoutSubviews {
[super layoutSubviews];
[self.headImgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.offset(kSpace15);
make.size.mas_equalTo(CGSizeMake(50.f, 50.f));
// 在自动计算行高模式下 要加上的
make.bottom.equalTo(self.contentView.mas_bottom).offset(-kSpace15);
}];
[self.nickNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.headImgView.mas_right).offset(12.f);
make.top.offset(17.f);
}];
[self.jobWorkLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.nickNameLabel.mas_right).offset(8.f);
make.right.lessThanOrEqualTo(self.contentView.mas_right).offset(-kSpace15);
make.top.offset(21.f);
}];
[self.hospitalLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.headImgView.mas_right).offset(12.f);
make.top.equalTo(self.jobWorkLabel.mas_bottom).offset(6.f);
}];
[self.line mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.offset(0);
make.height.mas_equalTo(0.5f);
}];
}
复制代码
布局时两个注意点: · 全部子控件,都要依赖与self.contentView做为约束父控件,而不是self(cell) · 关键控件要作bottom约束 (由于再也不指定行高,因此要须要给出根据bottom的约束)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
WMDoctorEvaluateDescribeInputCell *cell = [tableView dequeueReusableCellWithIdentifier:[WMDoctorEvaluateDescribeInputCell reuseIdentifier] forIndexPath:indexPath];
kWeakSelf
cell.describeInputBlock = ^(NSString * _Nonnull describeText) {
weakSelf.inputDescribeText = describeText;
};
//关键的一步,解决不正常显示问题
[cell layoutIfNeeded];
return cell;
}
复制代码
这样就完成了自动适应高度的要求了。
另外: 针对一些自动适应高度很差作的cell,能够单独处理 以下:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 2) {
return [WMDoctorEvaluateStarCell staticHeight];
}
return UITableViewAutomaticDimension;
}
复制代码
在用UITableViewAutomaticDimension,有的界面比较复杂,虽然这样能完成显示,可是在滑动的过程当中,能肉眼感觉到卡 掉帧,众所周知 60fps是比较符合人眼审视的,若是帧数 低于这个数值过多,就会明显感觉到卡帧等现象,这属于优化性能方面的问题,因此就要思考一下怎样来达到优化tableview性能。
思路: 缓存高度机制
首先获取cell实际显示的高度
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *key = [NSString stringWithFormat:@"%ld", (long)indexPath.row];
[self.heightDict setObject:@(cell.height) forKey:key];
NSLOG(@"第%@行的计算的最终高度是%f",key,cell.height);
}
复制代码
//didEndDisplayingCell 当cell滑出屏幕时会触发此方法,是cell已经被真正的显示在了屏幕上,因此在这里打印出的高度必然是最正确的高度。根据indexPath.row做为key,将高度缓存进字典.
而后在 heightForRowAtIndexPath 方法里判断,若是字典里有值,则使用缓存高度,不然自动计算:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *key = [NSString stringWithFormat:@"%ld",indexPath.row];
if (self.heightDict[key] != nil) {
NSNumber *value = _heightDict[key];
return value.floatValue;
}
return UITableViewAutomaticDimension;
}
复制代码
注意:设置cell的预估高度时必定要设置最小高度cell的那个值。否则的话,在滑动的时候,当高度最小的那个滑动到一大半的时候,就会忽然一下消失,形成掉帧的现象。