UICollectionViewCompositionalLayout 是 Apple 在 iOS 13 引入的,用于构建基于组合的 UICollectionView 布局的类。它允许开发者根据一系列的组合布局,更轻松、更灵活地创建复杂的 UICollectionView 布局。

本文主要围绕 UICollectionViewCompositionalLayout 中 “可组合” 这个特性进行讨论。

本文建立在您已经大致了解过 UICollectionViewCompositionalLayout,知道它是什么,以及基础的用法。下文将不再对其进行讲解。

组合类 FlowLayout 布局

在上一篇文章 NSCollectionLayoutGroup 之子视图的填充 的最后有提到一个场景,这里复用一下这个场景:

在前文中,我有说中间的 “工具Group” 不能使用 subitems 的形式进行初始化,应该使用 subitem + count 的形式初始化 —— 其实这不完全对。

实际需求上,如图这种类 FlowLayout 的布局往往会要求 “每行X个”,即平分展示。

此时如果你们需要考虑下面的场景:

  • 屏幕旋转。
  • iPad 等屏幕尺寸会发生变化的情况。
  • AutoLayout 侧不想要获取屏幕尺寸去计算出 cell 宽度。

那么 subitem + count 就没法做了:subitem + count 会忽略布局方向上的尺寸,所以此时很难单纯用 AutoLayout 做出平分的效果,或者说为了实现平分,需要多次刷新页面布局。

此时有一种用 subitems 实现的方式:以每行/列作为一个 Group,Group 内使用 subitems。大致的代码如下所示:

let itemSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1/4), 
    heightDimension: .estimated(69)
)

let normalLayoutSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1), 
    heightDimension: .estimated(500)
)

let toolsListGroup = NSCollectionLayoutGroup.horizontal(
    layoutSize: normalLayoutSize,
    subitems: [ .init(layoutSize: itemSize) ]
)

let rows = 5
let toolsGroup = NSCollectionLayoutGroup.vertical(
    layoutSize: normalLayoutSize,
    subitems: [
        [toolsTitleItem],
        Array(repeating: toolsListGroup, count: rows),
        [
            toolsFooterItem,
            sectionPaddingItem
        ],
    ].flatMap { $0 }
)

上面的代码首先定义了2个 Size,一个是 Item 的尺寸,另外一个算是一个 “占位符”。

然后是2个 Group:

  • toolsListGroup 以行为单位展示 cell。
  • toolsGroup 则是最外面的 Group,它的 subitems 包含多个不同的 Group。其中的 Array(repeating: toolsListGroup, count: $0) 则是根据行数生成对应数量的 Group。

这种写法有一个注意点还有一个疑惑:

  • 注意点是:当 cell 的数量不是行的整数倍的时候,需要手动创建占位 cell。
    举例来说,cell 有 5 个,对应的就是2行,cell 分布是 4 + 1。此时因为后面还有其他等待被填充的 Item/Group,如果没有创建占位 cell,则后面的 cell 会被提前,使用 toolsListGroup 里的 Item 布局。

  • 疑惑的是:我并不知道为什么这么做能生效。
    按理说 subitems 的布局方式是不会限制内容的数量的,那么为什么它能每行正好装4个(就这个例子来说,每个Cell的宽度是屏幕的1/4),下一行的 Cell 则使用下一个 Group 的布局?

有关于 UICollectionViewCompositionalLayout 组合复杂布局的文档还是太少,目前还没找到这么做能生效的原因。