cell高度自适应

cell自适应的怨念

iOS7中估算cell 高度的方法:estimatedRowHeight

1
2
3
4
5
6
7
self.tableView.estimatedRowHeight = 88;

// or

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// return xxx
}

估算高度出现的问题:

  1. 设置估算高度后,contentSize.height 根据“cell估算值 x cell个数”计算,这就导致滚动条的大小处于不稳定的状态,contentSize 会随着滚动从估算高度慢慢替换成真实高度,肉眼可见滚动条突然变化甚至“跳跃”。
  2. 若是有设计不好的下拉刷新或上拉加载控件,或是 KVO 了contentSizecontentOffset属性,有可能使表格滑动时跳动。
  3. 估算高度设计初衷是好的,让加载速度更快,但损害了滑动的流畅性,滑动时实时计算高度会带来明显的卡顿。

iOS8中self-sizing cell——cell自己负责对自身高度的计算

有两种方式:

  1. fram layout下:重写sizeThatFits方法
  2. auto layout下: add constraints to cell.contenView

这个特性只支持iOS8以上的系统,用到的API:

1
2
self.tableView.estimatedRowHeight = 213;
self.tableView.rowHeight = UITableViewAutomaticDimension;

所以,需要支持iOS7的项目,还是需要自己实现高度自适应。

第三方实现——FDTemplateLayoutCell

FDTemplateLayoutCell的GitHub地址

所有的附加文件都是UITableView的category,共有四个类:

  • UITableView+FDTemplateLayoutCell
  • UITableView+FDKeyedHeightCache
  • UITableView+FDIndexPathHeightCache
  • UITableView+FDTemplateLayoutCellDebug

UITableView+FDTemplateLayoutCell

这个category把tableView中用到的不同样式的cell封装成一个一个的模板cell,然后计算该模板cell的高度。

高度计算过程

  1. 使用auto layout计算高度(cell使用了auto layout):通过systemLayoutSizeFittingSize:计算cell高度

    1
    fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
  2. 警告:一旦在使用auto Layout的情况下,步骤1返回的高度为0,则报警告

  3. 使用frame layout计算高度(cell可能没有使用auto layout):如果步骤1返回高度为0,则调用sizeThatFits:方法计算cell高度

  4. 如果返回高度仍为0,就自己给一个合适的高度或默认的高度44,然后根据需要把高度给缓存类或者UITableView的heightForRow

注意:

计算得到的高度不包括 separator view的高度,所以在返回高度之前,会根据separator style来增加1px

1
2
3
if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
fittingHeight += 1.0 / [UIScreen mainScreen].scale;
}

UITableView+FDKeyedHeightCache

​ 根据key,比如reuseID,给cell做高度缓存。

​ 比如,有三种不同类型的cell,每种cell的高度的高度是一样的,但三种类型各不相同,这时候可以用这个方法缓存高度。

UITableView+FDIndexPathHeightCache

​ 根据indexPath进行高度缓存。

​ 比如在tableView reloadData的时候,如果根据indexPath做了缓存,就不需要再次计算

UITableView+FDTemplateLayoutCellDebug

​ 主要是在debug的时候添加一些log信息,方便debug

cell自适应高度的自定义实现

auto layout下:

  1. 给cell的subViews添加合适的约束

  2. 给tableView设置估算高度

  3. cell.contentView添加约束。因为cell.contentView必须要有super view(就是tableView)才能知道自身的宽度,然后才能保证layout出合适的高度。

    给contentView添加约束:

    1
    2
    3
    4
    5
    6
    7
    CGFloat contentViewWidth = CGRectGetWidth(self.tableView.bounds);
    NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth];

    [cell.contentView addConstraint:widthFenceConstraint];
    // Auto layout engine does its math
    fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    [cell.contentView removeConstraint:widthFenceConstraint];
  4. 在heightForRow的时候,创建cell——>这个cell只用来计算需要的cell高度,并不绘制在tableView上,因为自适应条件下要获取cell高度,需要根据cell内容来计算cell高度,所以首先要有一个cell。

注意:cell.contentView的约束是在获取height的方法里添加的,里面根据accessoryType的不同设置了不同的contentView的宽度(accessoryType不同,accessory的宽度不包含在contentView中)。