Compositional layout是在2019年为UICollectioinView引入的一个新布局
Compositional layout是什么
Compositional layout是一套针对UICollectionView新的布局方法- 对应的核心类是
UICollectionViewCompositionalLayout(macOS上是NSCollectionViewCompositionalLayout) - 其目的是让
UICollectionView可以更容易地支持更灵活布局UI的开发
Compositional layout布局的三大设计哲学:
Composable:可组合,强调用简单的组件组合出复杂的内容Flexible:灵活(官方说,You can write any layout with Compositional layout)Fast
几个例子感受一下Compositional layout能做什么
- 如下示意图展示了一个纵向滚动的
UICollectionView,其中有上中下三部分,上部分看上去像两列UITableView,各部分的布局和样式各不相同

- 如下示意图展示了纵向滚动的
UICollectionView,其中横向上有多个可以横向滚动的组(App Store应用大量使用该布局)- 看到这里我立马想到了:可能再也不用多个
UICollectionView嵌套了
- 看到这里我立马想到了:可能再也不用多个

四个核心概念
Compositional layout由四个最核心的概念组成
Item > Group > Section > Layout
- 从
Item到Layout,表示的范围依次扩大 - 任何一个
Compositional layout都从左到右组合而成

如何使用Compositional layout
Compositional layout的核心类是UICollectionViewCompositionalLayout,其初始化方法有两类,如下代码所示:
- 一类是直接提供
section,另一类是通过provider动态的提供section
swift
class UICollectionViewCompositionalLayout : UICollectionViewLayout {
public init(section: NSCollectionLayoutSection)
public init(section: NSCollectionLayoutSection, configuration: UICollectionViewCompositionalLayoutConfiguration)
public init(sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider)
public init(sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider, configuration: UICollectionViewCompositionalLayoutConfiguration)
}
创建UICollectionViewCompositionalLayout的过程就是上小节提到的
Item > Group > Section > Layout
less
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
OrthogonalScrolling
再介绍一个官方Demo中提到的稍微复杂一点的Compositional layout案例
我们希望最终效果如下图所示:

首先进行设计:
- 整体纵向滚动,横向上有多行,每一行也是可以滚动的,每一行我们可以看做是一个
Section - 关注到每一行中的元素,每一行中基本的滚动单元是:左侧的一个大块+右侧两个小块,滚动单元可以认为是
Group - 具体的大小块则可以认为是
Item
关于"左侧的一个大块+右侧两个小块"的示意图如下所示:

以下是Compositional layout代码,我们对照注释看一下创建过程:
less
// 1. 左侧大块Item的创建
// - 宽度:希望占容器(group)宽度的70%
// - 高度:希望和容器一样高
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(1.0)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
// 2. 右侧任意的一个小块
// - 宽度:和容器一样宽
// - 高度:占容器高度的一半
let trailingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.5)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
// 3. 创建一个容器group,这是一个纵向的容器,容纳右侧的两个小块Item。宽度占该group所在容器的30%;高度和容器一致
let trailingGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0)),
repeatingSubitem: trailingItem,
count: 2)
// 4. 创建一个横向容器group,容纳1个大块+2个小块。宽度占其容器的85%,高度占40¥
let containerGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.85),
heightDimension: .fractionalHeight(0.4)),
subitems: [leadingItem, trailingGroup])
// 5. 创建section,包含最外层的横向容器group
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .continuous
// 6. 创建layout,使用默认configuration,默认是纵向滚动
return UICollectionViewCompositionalLayout(section: section)
再看一下数据源代码:
ini
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
var identifierOffset = 0
let itemsPerSection = 30
for section in 0..<5 {
snapshot.appendSections([section])
let maxIdentifier = identifierOffset + itemsPerSection
snapshot.appendItems(Array(identifierOffset..<maxIdentifier))
identifierOffset += itemsPerSection
}
- 整体数据是一个二维数组,类似这样:
[[0,1...29], [30...59], [...], [...], [...]] - 创建了5个
Section,每个Section - 每个
Section中有30个数字 - 30个数字拆分成了10个
Group,每个Group有三个Item。如果对应着数据源,则依次是[0,1,2], [3,4,5]..... - 每个数字表示一个
Item
其他
UICollectionLayoutListConfiguration.Appearance

orthogonalScrollingBehavior
orthogonal(发音:/ôrˈTHäɡən(ə)l/):正交。但并非数学上的概念,而是指,与指定方向是正交方向的另一个方向。说白了,如果制定的滚动方向是垂直,则orthogonalScrolling(正交滚动方向)就是水平方向
问题
1. 如何横向滚动
Demo中都是纵向滚动的,Compositional layout是否支持横向滚动?
当然,如下所示,不过要注意一下写法
- 自定义
UICollectionViewCompositionalLayoutConfiguration,设置scrollDirection即可
如果尝试修改因
UICollectionViewCompositionalLayout(section: section)而自动创建的UICollectionViewCompositionalLayoutConfiguration.scrollDirection可能不起作用
ini
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)
1. iOS 26.3中带count参数的Group初始化方法有bug
- 通过
horizontal(layoutSize:repeatingSubitem:count:)创建的group,无法做到按照count对item等分布局 - 但通过
horizontal(layoutSize:subitems:)或者已经废弃的horizontal(layoutSize:subitem:count:)可以正确实现
按照如下代码中所示的:
less
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 3)
2. 官方Demo-AdaptiveSections的bug
- 官方Demo中,
AdaptiveSections部分,会根据容器宽度决定一行显示的列数 - 但在iOS 26.3中测试,旋转到横屏后,列数并没有按照预期增加
- 未定位到原因