iOS TableViewcell 高度计算+动态高度

开发中tableView的cell高度分为写死的高度和动态的高度,下面是本人自己总结的一些操作。


固定cell高度:

cell高度分为等高和不等高,对于等高的情况,很简单,直接设置tableView.rowHeight即可,或者是在代理方法里heightForRowAtIndexPath: 里返回一个固定值就行了;在实际应用中,cell的高度往往是可变的,需要根据显示内容大小或者是屏幕尺寸动态变化。


不固定cell高度

对于不固定cell高度,总共种情况

    1. tableView代理返回高度模式
    1. 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自动计算。

核心思想

  1. 首先要让自动适应内容的子控件与cell建立联系。比如在Xib中,之前我们约束一个高度可变的label,通过约束它距左边距上边的距离,在约束宽度后这个label,已经确定了,此时我们再将label的底部和cell的底部建立约束关系。
  2. 添加cell估算代码和cell高度的代码
    tableView.estimatedRowHeight = 44;
    tableView.rowHeight = UITableViewAutomaticDimension;
    estimatedRowHeight高度最好设置成最贴近cell本身高度,因为自动估算高度不准可能会导致右侧滚动条不精确,跳动。否则就隐藏掉。。
  3. 不用代理方法返回cell高度,运行程序,cell高度自动适应了。

这个方法最核心主要在让子控件和父控件建立联系。

Example
  • xib:
    label高度不用设置,左右约束,top,bottom约束即可。


    iOS TableViewcell 高度计算+动态高度_第1张图片
  • 代码:(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];
}

你可能感兴趣的:(iOS TableViewcell 高度计算+动态高度)