开发中tableView的cell高度分为写死的高度和动态的高度,下面是本人自己总结的一些操作。
固定cell高度:
cell高度分为等高和不等高,对于等高的情况,很简单,直接设置tableView.rowHeight
即可,或者是在代理方法里heightForRowAtIndexPath
: 里返回一个固定值就行了;在实际应用中,cell的高度往往是可变的,需要根据显示内容大小或者是屏幕尺寸动态变化。
不固定cell高度
对于不固定cell高度,总共种情况
- tableView代理返回高度模式
- AutoLayout自动计算模式
1. TableView代理返回高度模式
介绍:
可通过frame进行布局,属于最基础的布局方式了,代码简单。
缺点
是cell复杂,存在内容动态显示时,代码比较难以维护,视图布局需要一套统一的布局链, 否则一不小心一团遭
。
也可通过Masonry进行布局,优点是约束好了后,修改一个视图布局,相关视图自动改变位置。
这种模式设置cell高度的一般的方法是给模型增加一个辅助属性的cellHeight
具体实现:
直接在模型里面加上cellheight高度属性,因为模型里面的数据就决定了cell的高度,重写cellheight的getter方法,里面直接利用数据把高度计算出来,赋给_cellheight。这样只需要在代理方法中返回高度即可。
计算高度最好给cell计算,因为cell内部有计算所需要的字体,宽度,间距等数据。
- model get方法中计算高度 并缓存
#import "SSModel.h"
@implementation SSModel
- (CGFloat)cellHeight {
if (_cellHeight == 0) {
_cellHeight = [SSCell calculateCellHeight:self];
}
return _cellHeight
}
@end
- cell负责根据模型数据计算高度
#import "SSCell.h"
#define kLabelWidth = 200
#define kLabelTop = 10
#define kLabelBottom = 10
@interface SSCell()
@end
@implementation SSCell
+ (CGFloat)calculateCellHeight:(SSModel *)model {
// 伪代码 通过模型数据计算高度
CGFloat labelHeight = [model.text boundingRectWithSize:xxx];
CGFloat height = kLabelTop + labelHeight + kLabelBottom;
return height;
}
@end
- tableView代理返回缓存的高度
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
SSModel *model = _dataArray[indexPath.row];
return model.cellHeight;//高度模型内已经计算。
}
2. AutoLayout自动计算模式
iOS8以后有一个自动计算cell高度的方法。
这个方法不需要代理方法返回高度了,直接通过AutoLayout自动计算。
核心思想
- 首先要让自动适应内容的子控件与cell建立联系。比如在Xib中,之前我们约束一个高度可变的label,通过约束它距左边距上边的距离,在约束宽度后这个label,已经确定了,此时我们再将label的底部和cell的底部建立约束关系。
- 添加cell估算代码和cell高度的代码
tableView.estimatedRowHeight = 44;
tableView.rowHeight = UITableViewAutomaticDimension;
estimatedRowHeight高度最好设置成最贴近cell本身高度,因为自动估算高度不准可能会导致右侧滚动条不精确,跳动。否则就隐藏掉。。- 不用代理方法返回cell高度,运行程序,cell高度自动适应了。
这个方法最核心主要在让子控件和父控件建立联系。
Example
-
xib:
label高度不用设置,左右约束,top,bottom约束即可。
代码:(Masonry)
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView.mas_top).offset(10);
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView.mas_bottom).offset(-20);
}];
进阶-cell动态改变高度
针对Masonry布局的cell。 xib cell动态改变高度需要将高度约束拖入代码。
- remake方式
- (void)change {
[label mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView.mas_top).offset(30); //改成30
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView.mas_bottom).offset(-20);
}];
// 回调tableView reloadData
}
remake方式会清除之前该所有的约束。对于布局改动较大的,比较方便。如果只针对一个约束进行更新。使用update
- update方式
- (void)change {
[label mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView.mas_top).offset(30); //改成
}];
// 回调tableView reloadData
}
update方式更新只能更新之前存在的约束。
所以update内写
make.top.equalTo(self.otherView.mas_top).offset(30);
是无效的。因为label的顶部距离cell顶部
和 label顶部距离otherView顶部
是两个约束。这种情况可以使用remake,但是不够优雅,因为要重新书写一遍其他的约束,代码冗余。 还有一种优雅的方式:约束activate/deactivate+优先级
- 约束activate/deactivate+优先级
我们可以使用Masonry提供的api,控制一个约束是否激活。
@interface SSCell()
@property(nonatomic, strong) MASConstraint *labelTopConstraint;
@end
- (void) createConstraint {
[self.label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView.mas_top).offset(10).priority(100);
self.labelTopConstraint = make.top.equalTo(self.otherView.mas_top).offset(30);
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView.mas_bottom).offset(-20).priority(300);
}];
[self. labelTopConstraint deactivate]; // 失效
}
- (void)change {
[self. labelTopConstraint activate]; // 生效
// 回调tableView reloadData
}
上述代码控制label顶部的约束有两个:
- 约束A:顶部距cell顶部10
- 约束B:顶部距离otherView顶部20
约束B刚开始失效,所以约束A产生作用。
chang事件后,约束B生效,label顶部同时出现两个约束,由于约束A优先级低,所以约束B产生作用。cell高度就动态改变了。
说明: 不管是什么方式动态改变了cell的高度,都要调用[tableView reloadData] 整体刷新tableView,否则不会重绘,改变不能立即生效,需要滑动复用了才行。
可能遇到的问题
-
约束警告问题
有时会爆约束警告,一般就是同一尺寸或者同一方向进行了过约束问题,可以在打印台查看问题或者打符号断点。如果实在找不到约束设置的问题,可以尝试将其中一个尺寸的约束优先级设低。
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView.mas_top).offset(10);
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView.mas_bottom).offset(-20).priority(300);
}];
-
masonry布局cell 刷新后tableView抖动问题。
使用autoLayout方式自动撑大的cell,重新reloadData的时候,tableView偏移量可能发生改变或者抖动。
#pragma mark - 解决动态cell高度 reloadData刷新抖动的问题
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
_estimatedRowHeightCache[indexPath] = @(cell.frame.size.height);
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [_estimatedRowHeightCache[indexPath] floatValue] ?:1.0;
}
将每次的cell高度按indexPath或者indexPath+cell类型的方式缓存下来,在上述方法返回。 第一次缓存高度为0时必须返回一个不为0的高度,否则cell显示不出来。
-
cell为XIB时,label不换行,滚动刷新后才换行的问题。
要将label的numberOfLines属性设置为0,不然不会自动分行。
还要设置下面代码;
- (void)awakeFromNib {
[self layoutIfNeed];
}