本篇主要讲解 NSCollectionLayoutGroup 内 “子视图” 的填充方式。
算是为 UICollectionViewCompositionalLayout 的讲解做一些铺垫,毕竟把 Item 的填充方式了解清楚了之后,才能组合多种 Group 来进行更复杂的布局。

NSCollectionLayoutGroup 不考虑方向的话,有两种填充子视图的方法。

概览

通过头文件您能查到如下几个初始化方法:

open class func vertical(layoutSize: NSCollectionLayoutSize, subitems: [NSCollectionLayoutItem]) -> Self

@available(iOS, introduced: 13.0, deprecated: 16.0)
open class func vertical(layoutSize: NSCollectionLayoutSize, subitem: NSCollectionLayoutItem, count: Int) -> Self

@available(iOS 16.0, *)
open class func vertical(layoutSize: NSCollectionLayoutSize, repeatingSubitem subitem: NSCollectionLayoutItem, count: Int) -> Self

@available(iOS, introduced: 16.0, deprecated: 16.0, renamed: "vertical(layoutSize:repeatingSubitem:count:)")
public class func verticalGroup(with size: NSCollectionLayoutSize, repeatingSubitem subitem: NSCollectionLayoutItem, count: Int) -> NSCollectionLayoutGroup

第三个方法是 iOS 16 新出的,同时废弃了第二个方法,而第四个方法在 iOS 16 引入同时又在 iOS 16 废除。
后文将围绕前两个方法展开讨论,不涉及到后两个方法。

subitem + count

这种初始化方式将在每个 Group 中重复 count 个 item。使用这种方法进行初始化时,在不同的方向上,layoutSize 会有不同的表现:

vertical

func vertical(layoutSize: NSCollectionLayoutSize, subitem: NSCollectionLayoutItem, count: Int) -> NSCollectionLayoutGroup

在纵向上,NSCollectionLayoutItemwidthDimension 生效,但是heightDimension 将被忽略。

Item 的实际高度会是 .fractionalHeight(1/count)

假设现在有下面这些代码:

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .absolute(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(100))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 2)

此时 Item 的实际高度会是 50,相当于一个高度为 100 的 Group 内有2个高度为 50 的 Item。

horizontal

func horizontal(layoutSize: NSCollectionLayoutSize, subitem: NSCollectionLayoutItem, count: Int) -> NSCollectionLayoutGroup

和 vertical 时的情况相对应:

在横向上,NSCollectionLayoutItemheightDimension 生效,但是widthDimension 将被忽略。

Item 的实际宽度会是 .fractionalWidth(1/count)

这里就不再用代码举例说明了。

subitems

和上一种初始化方法相比,使用 subitems 的方式来进行初始化要简单的多,因为它内部的 Item 的大小完全取决于 Item 自身。考虑如下代码:

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .absolute(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(300))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])

该示例中,每个 Item 的高度就是 100,而宽度是 Group 的 1/3。每个 Group 中有多少个 Item 取决于数据源传递给 UICollectionView 的数量。

有一点需要注意的是,Group 只是用来辅助布局的,它本身并不会生成任何视图。所以尽管在本示例中它的高度只有 300,不足 UICollectionView 的高度,但是 Item 依然会充满整个 UICollectionView,而不是仅仅在高度为 300 的范围内进行滑动。

我并不能肯定这种现象可以称之为 “忽略 Group 的 layoutSize”,所以没有使用这种表述。

总结

这两种初始化方式各有各的用途:

在不考虑嵌套布局(Group 中嵌套 Item 和 Group)的情况下,这两种布局的使用场景大体上一致,只需要注意各自对 Item 大小的计算规则即可。绝大多数情况下我自己会选择 subitems 的初始化方式,因为 Item 的大小计算规则更为直观。

但是在嵌套布局的前提下,这两种初始化方式就会有比较大的差异。

首先嵌套布局的最外层只能使用 subitems 的方式初始化,数组中包含多种不同的 Item 或者 Group。
其次如果需要指定 Item 的数量,那么就只能使用 subitem + count 的初始化形式。

考虑下图的场景:

如果现在要求顶部的标题、中间的工具和下方的灰色分隔条,三部分放到一个 NSCollectionLayoutGroup 对象中,可以使用 [标题Item + 工具Group + 灰条Item] 的组合布局形式。

此时 “工具Group” 就不能使用 subitems 的方式进行初始化了,必须要指定工具的数量,否则 UICollectionView 会把下方的灰条也融到工具 Group 中。