Skip to main content

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

· 6 min read

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

自适应宽高的集合视图 Cell​

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

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

步骤1: 设置 estimatedItemSize:​

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

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, 仅仅是一套可以正常执行的代码):
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 的文字距离边缘的内缩貌似有些困难, 需要找到合适的办法.