一次搞懂 iOS 组合布局:用 CompositionalLayout 打造马赛克 + 网格瀑布流
最近在项目中遇到一个很有趣的需求:
要实现一个"左边大图 + 右边 2×2 小图"的马赛克布局,下面再接一个三列网格瀑布流。
乍一看,有点像短视频 App 的推荐页或者流媒体内容流。
以往这种复杂布局,我们可能会:
- 写自定义 FlowLayout
- 手动算每个 cell 的 frame
- 各种 if else 填满屏幕
- 容易出错且维护难度大
但今天我才发现 ------ 用 UICollectionViewCompositionalLayout 能把这种布局写得既优雅又清晰!
🎯 需求拆解
视觉结构如下:

总结:
-
Section 0:前 5 个元素
- 左侧:1 个大图(宽度 50%,高度与右侧一致)
- 右侧:4 个小图(2 × 2)
-
Section 1:剩余元素进入 3 列网格布局
适用场景:
- 首页推荐流
- 杂志式内容展示
- 多风格 Feed 流
- Banner + 内容区结合布局
🧠 为什么用 UICollectionViewCompositionalLayout?
Apple 官方定位:
Compositional Layout = 用组合方式表达复杂布局的声明式系统
优势:
- 完全声明式:不用操心 layoutAttributes
- 多 Section 自由组合:每个 section 可以完全不同
- 适合不规则布局:左右不对称、嵌套 group、水平 + 垂直混合滚动
对于我们的"马赛克区 + 网格区"需求,简直天生适合。
📐 核心知识点(深入版)
1️⃣ Item(最基本元素)
Item 就是 UICollectionView 中的 cell 对应的布局单元 。
它不仅控制单个 cell 的尺寸,还能设置间距、对齐方式。
关键属性:
-
widthDimension/heightDimension.fractionalWidth(0.5):相对于父 Group 宽度的一半.fractionalHeight(1.0):填满父 Group 高度.absolute(100):固定尺寸.estimated(150):动态高度,适合自适应内容
-
contentInsets- 控制 cell 与 cell 之间的间距
- 例如:
NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4) - 避免手动计算 frame,也能保证布局美观
💡 Tip:在嵌套 Group 中,Item 的 fractional 高度是相对于 直接父 Group 的高度。
2️⃣ Group(组合 Item 的容器)
Group 是 把若干 Item 或 Group 组合起来的容器 ,可以是水平(horizontal)或垂直(vertical)。
理解 Group 是理解 CompositionalLayout 的关键。
核心概念:
-
水平 Group
-
将多个 Item 横向排列
-
例如右侧 2 个小格子在同一行:
lessNSCollectionLayoutGroup.horizontal(layoutSize: rowSize, subitem: smallItem, count: 2)
-
-
垂直 Group
-
将多个 Item 或水平 Group 纵向叠加
-
例如右侧 2x2 小图:
- 先把一行两个小图组合成水平 group
- 再把上下两行叠加成垂直 group
-
-
嵌套 Group
- 左右布局:把左侧大图和右侧小图列组合成一个水平 Group
- 核心思路:从最小的元素开始组合,逐层嵌套,最终形成完整 Section
💡 Tip:Group 的尺寸也是 fractional,相对父 Group 或 Section,保证屏幕适配。
3️⃣ Section(整个区域)
Section 是 最终展示区域,包含一个或多个 Group。
Section 的作用:
-
定义整个布局区域的 边距、间距
section.contentInsets控制整个区域与 CollectionView 边界的距离
-
可以为 Section 添加 Header / Footer
-
Section 可以自由组合,形成多风格布局
直观理解:
- Item = 一张卡片
- Group = 小版面 / 组合卡片
- Section = 整个版块
4️⃣ 布局比例理解技巧
-
用 fractionalWidth/fractionalHeight 控制比例
- 宽高比例可以随屏幕自适应
- 保证布局在 iPhone / iPad 上一致
-
嵌套顺序
- 先从最小的 Item 开始设置尺寸
- 再组合成水平 / 垂直 Group
- 最后组合成 Section
- 这样思路清晰,容易调整
-
间距管理
- Item.contentInsets 控制 cell 内间距
- Section.contentInsets 控制整体区域边距
- 避免在 Group 中重复设置过多间距
5️⃣ 总结思路
如果用一句话概括 CompositionalLayout 的核心逻辑:
"先定义每个 Item,再组合成 Group,最后把 Group 放进 Section,层层嵌套,形成最终布局。"
这个思路放到我们的示例里就是:
- 左侧大图 → 一个 Item
- 右侧小图 → 小 Item → 水平 Row Group → 垂直 Column Group
- 主组 = 左大图 + 右侧 Column Group
- Section 0 = 主组 + contentInsets
- Section 1 = 三列网格
🛠 实战:完整 createCompositionalLayout() 方法
less
private func createCompositionalLayout() -> UICollectionViewLayout {
return UICollectionViewCompositionalLayout { [weak self] (sectionIndex, env) -> NSCollectionLayoutSection? in
guard let self = self else { return nil }
let spacing: CGFloat = 4.0
// Section 0:马赛克区 (左大右 2x2)
if sectionIndex == 0 {
let leftLargeItemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.5),
heightDimension: .fractionalHeight(1.0)
)
let leftLargeItem = NSCollectionLayoutItem(layoutSize: leftLargeItemSize)
leftLargeItem.contentInsets = .init(top: spacing, leading: spacing, bottom: spacing, trailing: spacing)
let smallItemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.5),
heightDimension: .fractionalHeight(1.0)
)
let smallItem = NSCollectionLayoutItem(layoutSize: smallItemSize)
smallItem.contentInsets = .init(top: spacing, leading: spacing, bottom: spacing, trailing: spacing)
let rightRowGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.5)
),
subitem: smallItem,
count: 2
)
let rightColumnGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.5),
heightDimension: .fractionalHeight(1.0)
),
subitem: rightRowGroup,
count: 2
)
let mainGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.5)
),
subitems: [leftLargeItem, rightColumnGroup]
)
let section = NSCollectionLayoutSection(group: mainGroup)
section.contentInsets = .init(top: spacing, leading: spacing, bottom: spacing, trailing: spacing)
return section
}
// Section 1:下方三列网格
else {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0/3.0),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(top: spacing, leading: spacing, bottom: spacing, trailing: spacing)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(1.0/3.0)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitem: item,
count: 3
)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 0, leading: spacing, bottom: spacing, trailing: spacing)
return section
}
}
}
✅ 完整实现了"左大右 2×2 + 下方三列网格",保持比例、间距和嵌套逻辑。
🔄 数据分区逻辑
前 5 个元素给 Section 0,剩下的给 Section 1。
swift
func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.isEmpty ? 0 : 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return section == 0 ? min(5, items.count) : max(0, items.count - 5)
}
数据分区 + CompositionalLayout,就能轻松实现复杂瀑布流效果。
✅ 总结
适用场景:
- 杂志排版
- 视频 / 图片内容流
- 商品推荐页
- Banner + 宫格混合布局
优势:
- 结构清晰
- 易维护
- 代码优雅
- UIKit 下的现代布局方式
如果你还在用 FlowLayout 手动计算 frame,真的要试试 CompositionalLayout!