一.使用约束(无需计算)
原理:cell的子控件通过垂直方向的约束将cell撑开。
比如下面这个tableView中的cell:
每个cell由三部分构成:顶部一个30px红色view,中间一个高度自适应内容的label,底部一个30px的绿色view。
如果我们想让cell高度自适应内容,可以这样设置约束:
cell里UI搭建的代码:
/** UI搭建 */
- (void)setUpUI {
//------- 红色view -------//
UIView *redView = [[UIView alloc] init];
[self.contentView addSubview:redView];
redView.backgroundColor = [UIColor redColor];
//------- label -------//
self.label = [[UILabel alloc] init];
[self.contentView addSubview:self.label];
self.label.font = [UIFont systemFontOfSize:20];
self.label.numberOfLines = 0;
//------- 绿色view -------//
UIView *greenView = [[UIView alloc] init];
[self.contentView addSubview:greenView];
greenView.backgroundColor = [UIColor greenColor];
//------- 建立约束 -------//
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(self.contentView);
make.height.mas_equalTo(30);
}];
[self.label mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(self.contentView);
make.top.mas_equalTo(redView.mas_bottom);
}];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.label.mas_bottom);
make.left.right.mas_equalTo(self.contentView);
make.height.mas_equalTo(30);
//===== 底部对齐是撑开cell的关键点 =====//
make.bottom.mas_equalTo(self.contentView);
}];
}
注意:
- label的宽确定、高不设置,可以实现label的高度自适应内容。
- 绿色view和cell的底部对齐是撑开cell的关键所在。
使用时注意:
- 需要给cell高度估计值,可以随便给,但不能不给:
tableView.estimatedRowHeight = 175;
- 不需要写
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
方法,写了的话cell高度以此代理方法为准。
二.使用frame(自己计算高度)
关于cell的高度,你可以把它看做两个部分:
- 高度固定的部分
- 高度不固定的部分
还是拿上面那个cell来说,它高度固定的部分是顶部红色view和底部绿色view;高度不固定的是label,而我们要计算的,就是高度不固定的那部分。
如何计算高度?
当然是在- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
里计算:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
SimpleFrameModel *model = self.dataArray[indexPath.row];
NSString *string = model.text;
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:string];
NSRange allRange = [string rangeOfString:string];
[attrStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:15.0] range:allRange];
[attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor darkGrayColor]range:allRange];
CGFloat titleHeight;
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
// 获取label的最大宽度
CGRect rect = [attrStr boundingRectWithSize:CGSizeMake(SCREEN_WIDTH, CGFLOAT_MAX)options:options context:nil];
titleHeight = ceilf(rect.size.height);
return titleHeight + 60; // 动态高度 + 静态高度
}
cell的高度就是高度固定部分(红色view的高度+绿色view的高度)与高度不固定部分(label的高度)的和。
三.如何提前计算高度?
滚动tableView时cell高度那个代理会不停回调,所以需要优化。
思路:
cell的高度由cell的内容决定,cell的内容由cell的model决定。也就是说,当我们获取到tableView的数据源时,其实就已经可以计算出所有cell的高度了。
1.将计算cell高度那段代码抽离出来
我写了个cell的类方法来计算cell高度:
/** 计算cell高度 */
+ (CGFloat)heightWithModel:(SimpleFrameModel *)model {
//SimpleFrameModel *model = self.dataArray[indexPath.row];
NSString *string = model.text;
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:string];
NSRange allRange = [string rangeOfString:string];
[attrStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:15.0] range:allRange];
[attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor darkGrayColor]range:allRange];
CGFloat titleHeight;
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
// 获取label的最大宽度
CGRect rect = [attrStr boundingRectWithSize:CGSizeMake(SCREEN_WIDTH, CGFLOAT_MAX)options:options context:nil];
titleHeight = ceilf(rect.size.height);
return titleHeight + 60; // 动态高度 + 静态高度
}
2.在获取到tableView数据源时计算cell高度并放到一个数组
//------- 数据源 -------//
self.dataArray = [NSMutableArray array];
//------- 缓存高度的数组 -------//
self.heightArray = [NSMutableArray array];
//------- 创建假数据 -------//
NSString *text = @"内容越来越多";
for (int i = 0; i < 100; i ++) {
text = [text stringByAppendingString:@"越来越多"];
SimpleFrameModel *model = [[SimpleFrameModel alloc] init];
model.text = text;
[self.dataArray addObject:model];
// 计算cell高度
CGFloat cellHeight = [SimpleFrameCell heightWithModel:model];
// 将高度放进高度数组
[self.heightArray addObject:[NSNumber numberWithFloat:cellHeight]];
}
3.需要cell高度时直接在高度数组里获取即可:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *cellHeight = self.heightArray[indexPath.row];
return cellHeight.floatValue;
}
四.总结
实现cell高度提前计算只需一个数组及几行代码,逻辑也非常简单,然而带来的优化效果却是非常显著的,我自己测试的时候已深有感触。
对于不只一个组的tableView,需要二维数组或字典,注意变通。
五.详细demo
欢迎交流。
2018年3月21日更新
去掉了高度缓存数组,给model添加了一个cellHeight属性:
@interface SimpleFrameModel : NSObject
@property (nonatomic, copy) NSString *text;
@property (nonatomic, assign) float cellHeight;
@end
拿到数据的时候就将高度算好:
//------- 数据源 -------//
self.dataArray = [NSMutableArray array];
NSString *text = @"内容越来越多";
for (int i = 0; i < 100; i ++) {
text = [text stringByAppendingString:@"越来越多"];
SimpleFrameModel *model = [[SimpleFrameModel alloc] init];
model.text = text;
// 计算高度,赋值给model
model.cellHeight = [SimpleFrameCell heightWithModel:model];
[self.dataArray addObject:model];
}
需要高度的时候直接从model中取:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
SimpleFrameModel *model = self.dataArray[indexPath.row];
return model.cellHeight;
}
相应代码已更新到github
网友评论