集合视图中使用 FlowLayout 时的高度自适应 Cell (self sizing cells)

本文最后更新于 2021年4月4日 晚上

集合视图中的 Cell 和 TableView 中的一样, 是可以自适应高度的(Height based on content), 前提是使用的 FlowLayout. 就用这篇文章来记录一下实现高度自适应 Cell 的一般流程, 文中大部分内容基于这篇文章翻译而来, 更多关于集合视图布局和高级效果的内容, 详见 Youtube 上的Raywenderlich 集合视图视频教程.

自适应宽高的集合视图 Cell

Self Sizing Cells 可以指定单轴(宽度或高度)或双轴(宽度和高度)都根据内容来自适应, 一般而言, 只需简单两步, 即可让 Cell 的宽度或高度自适应.

但需要注意的是, 系统自带的宽高自适应能力仅限使用 UICollectionViewFlowLayout 作为布局类的情况.

步骤1: 设置 estimatedItemSize:

首先要设置布局对象的 estimatedItemSize 属性, 这样系统才会参与进行自适应的尺寸计算:

1
layout.estimatedItemSize = CGSizeMake(1.f, 1.f);

实际上进行了这一步, 对于两轴尺寸就都可以自动适应了(在 iOS 12 beta 版上测试时行为异常, 其他版本正常, 看 iOS 12 正式版后会否有问题).

但如果实际需求某个轴是固定尺寸, 而另外一个轴根据内容调整高度(比如高度自适应), 此时就需要进行步骤 2 的工作了.

步骤2: 设置 ContentView 的约束

决定单轴还是双轴自适应的条件有:

  1. 是否在 Cell 的 ContentView 上设置有约束

  2. ContentView 的约束是宽度方向上的还是高度方向上的(如果两个方向都有约束就不再是自适应的了, 不在本文的讨论范围内)

一定注意约束是设置在集合视图的 Cell 的 ContentView 上的, 由于在 Xib 中看不到集合视图的 ContentView, 故这个步骤只能在代码中执行.

假设只设置宽度方向的约束, 则高度就可以根据内容进行自适应了.

但自适应的前提是: 内容中的高度或是提前确定, 或者是指定有高度约束, 或者是本身就有固有内容尺寸.

下面就分步骤来做:

  1. 建立基本代码结构, 如下所示(下面的代码中还没有注册我们自定义的 Cell, 仅仅是一套可以正常执行的代码):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import UIKit
import SnapKit

class AutoHeightCollectionViewController: UIViewController {

private var _collectionView: UICollectionView!
private var _flowLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 1.0, height: 1.0)
layout.minimumLineSpacing = 8.0
layout.minimumInteritemSpacing = 8.0
layout.sectionInset = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
return layout
}()

override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}

private func setupCollectionView() {
_collectionView = UICollectionView(frame: .zero, collectionViewLayout: _flowLayout)
_collectionView.dataSource = self
_collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(_collectionView)
_collectionView.snp.makeConstraints { $0.edges.equalToSuperview() }
}
}

extension AutoHeightCollectionViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 2
}

func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .red
return cell
}

}
  1. 新建 Cell 以及对于的 Xib, 如果要高度自适应, 则应该在 contentView 上设置宽度约束, 同理如果要宽度自适应, 可以设置高度约束.

如果两个方向都不设置, 则是两方向的自适应.

Xib 中 Cell 新建出来是没有 contentView 的(和 TableViewCell 不同), 但实际上在 Xib 中的 Cell 框框里面添加的视图会被直接作为 ContentView 的子视图, 这点算是一个隐藏关卡…

至此, 自适应 Cell 的编写就算完成了.

一些注意点

  1. 如果不设置 Cell 的宽以及高约束, 对于单行的 Label 而言, 如果它的宽度(固有内容尺寸)超过了 collectionView 的宽度, 就会报错… 这个是个大坑!

  2. 对于多行文字的 Label(这时的 Label 是没有宽度方向上的固有内容尺寸的), 如果没有设置宽度, 肯定会报错!!!

  3. 自适应场景下, 想要设置 Label 的文字距离边缘的内缩貌似有些困难, 需要找到合适的办法.


集合视图中使用 FlowLayout 时的高度自适应 Cell (self sizing cells)
https://blog.rayy.top/2018/09/16/2019-3-CollectionViewAutoHeightCell/
作者
貘鸣
发布于
2018年9月16日
许可协议