iOS开发:UITableView自适应高度

iOS

人都是想偷懒的,技术因为”偷懒”而进步。造出来的轮子,让键盘上的双手得以腾出空来,可以干点别的事情。比如画画比如写书,所以我们有黑客与画家。

从iOS8开始,iOS提供了对UITableViewCell自适应高度的支持。而在此之前,假如你需要编写一个异构的列表,你可能会觉得很不想动手:你需要计算每一种结构的Cell的高度,-tableView:heightForRowAtIndexPath:会是你最不想看到的方法之一。

所幸的是,你现在只需要设置,_tableView.rowHeight = UITableViewAutomaticDimension,大体来说,这样就可以了。Cell内部使用AutoLayout,对于这样异构的列表,并不大影响它的用户端性能表现。

# iOS8的魔法

iOS8之前,UITableView并无自适应高度一说。所谓的自动适配高度,即是手动计算并触发重新布局,具体细节就不赘述了。

那么转念一想,iOS8难道就不一样了吗?如果你理解Auto Layout,自然是知道,Auto Layout是布局系统在计算布局前的一个约束系统。在更新界面布局前,通过约束计算可以得出实际的布局数据,然后按传统流程,触发界面重新布局及渲染了。核心确实是一样的。

理解这个很重要,出现问题时可以按图索骥来解决。

iOS8在UITableView这个UI组件层面加上自适应高度的支持,可谓聊胜于无,但对一些特定业务开发来讲亦是省心省力。

关键代码示范:

1
2
_tableView.estimatedRowHeight = 100;
_tableView.rowHeight = UITableViewAutomaticDimension;

没有开玩笑,这几乎是UITableView自适应高度需要的所有代码了。

但有几个问题是需要注意的:

  1. Cell内部的Auto Layout约束,在y方向必须确保与contentView的top/bottom的连接能确定contentView的尺寸;
  2. rowHeight = UITableViewAutomaticDimension或者实现相应的代理方法;
  3. estimatedRowHeight需要设置;

# 常见的问题

  1. iOS 10(及以下)并没有自适应?

    请检查_tableView.estimatedRowHeight = xxx;是否设置了。在iOS11+,即使不设置该方法也是没问题的,但旧版本就没那么幸运了。如果没意识到这个,在设备兼容上你可能得多花一点时间来排查问题了。

  2. iOS11+也没自适应?

    请检查Cell内部的Auto Layout约束是否确保了Cell的contentView尺寸被subviews约束了。重点留意y方向top/bottom属性的约束。

  3. 部分Cell高度没自动更新高度

    请留意该Cell内部有没有子控件是UIScrollView的子类,这个问题请看下一节。

# UIScrollView 与 intrinsicContentSize

iOS6新增了一个UIView的属性方法,- (CGSize)intrinsicContentSize。学习Auto Layout最终必然要认识这一属性的。平时写布局时经常没设置UILabel、UIImage的大小,但出来效果毫不影响,正因为此属性的功劳。

什么是intrinsicContentSize呢?字面理解,称作固有大小。简单来说,如果布局系统没有给UIView指定大小,那么就按intrinsicContentSize作为其大小——言下之意,如果指定了视图大小,那断然是按指示办的。UILabel、UIImage、UIButton等UI组件是有固有大小的,根据字体、文本、图片来确定其大小。但不是所有View都有intrinsicContentSize的,UIView默认实现是返回UIViewNoIntrinsicMetric。UIScrollView因为其可滚动特性的特殊性,其intrinsicContentSize同样被设定为UIViewNoIntrinsicMetric。

这就是为什么当你遇到UITextView、UICollectionView等等作为Cell子视图时,魔法不奏效的原因了。

解决方法就是,指定UIScrollView布局的”固有内容大小”了。譬如,将UIScrollView嵌入到一个container中,指定container的intrinsicContentSize,不失为一种方法。

问题总是会遇到的,具体就请自行实践了。事不亲历,难说感同身受。

顺便提一下,与intrinsicContentSize相关还有一关键问题:假如两个组件都没显式约束了大小,但是就intrinsicContentSize布局的话,可能会发生冲突!此时,Content Hugging与Content Compression这两个配置可以帮助解决冲突的。用俗话讲,Content Hugging是不想变大约束,Content Compression是不想变小约束,优先级越高越符合其初衷。

# 其他问题

  1. 设备尺寸兼容问题

    可能要回退到手动设定高度。举个业务场景,比如可滚动(变长)的列表,希望尽可能内容铺满页面。在小尺寸设备内容会超过页面大小,而大尺寸设备内容无法铺满一屏、但希望铺满一屏。

    snapshot

    仅提供参考:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    CGFloat const kContentTopBottomHeight = Scale((408+368));
    CGFloat const kContentHeight = Scale(558);
    CGFloat height = MAX(kContentHeight, SCREEN_HEIGHT - kContentTopBottomHeight - TAB_BAR_SAFE_BOTTOM_MARGIN);
    if (SCREEN_HEIGHT >= height) {// 1屏
    return height;
    }
    return UITableViewAutomaticDimension;
    }
  2. UICollectionView自适应

    UICollectionView同样类似,需要使用layout.estimatedItemSize而不是layout.itemSize,具体要自行尝试。

  3. 性能问题

    使用Auto Layout后性能是会比手动布局稍低一些,具体视布局复杂度而定。

    性能敏感的请另行考虑方案。

# 小结

UITableView的高度自适应为我们某些业务场景的界面编写带来了些便利,可以显式地减少很多繁琐的计算,在复杂的多尺寸设备环境下可以说是很贴心了。并且对于不可重用界面减少自定义Cell,对整体项目而言也是有利的。

具体情况具体分析,也就各取所需了。

Author: Jason

Permalink: http://blog.knpc21.com/ios/ios-tableview-autoheight/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments