1. UICollectionView 的职责分离
UICollectionView
本质上只是一个容器 ,用来展示一系列的 cell(单元格)。
它本身 不关心 cell 的摆放方式,只负责:
-
Cell 的复用(避免性能浪费)
-
Cell 的增删改查
-
滚动与事件处理
👉 那么 Cell 怎么排版 ?Cell 大小是多少? 行间距、列间距、滚动方向是什么?
这些都不是 UICollectionView
的工作,而是交给 布局对象(Layout object)。
2. 为什么要有 UICollectionViewLayout
苹果采用了 策略模式(Strategy Pattern):
-
UICollectionView
负责数据展示和交互。 -
UICollectionViewLayout
负责计算布局。 -
这样
UICollectionView
就可以灵活切换布局策略,而不用修改控件本身。
例如:
-
想要网格布局,用
UICollectionViewFlowLayout
-
想要环形布局,可以自定义
UICollectionViewLayout
-
想要卡片轮播(类似 App Store),也可以写自定义布局
👉 这样就实现了 UI 展示与布局解耦。
3. UICollectionViewFlowLayout 的作用
UICollectionViewFlowLayout
是 Apple 提供的 默认布局方案,大多数场景够用。
它能做的事情包括:
-
滚动方向(水平 / 垂直)
-
行间距 、列间距
-
section 的内边距(sectionInset)
-
item 的大小 (固定大小,或通过
delegate
动态设置) -
header/footer 的大小
举例:
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical // 垂直滚动
layout.itemSize = CGSize(width: 100, height: 100) // 固定大小
layout.minimumLineSpacing = 10 // 行间距
layout.minimumInteritemSpacing = 5 // 列间距
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
这样,UICollectionView 就知道 如何排列每个 cell。
4. 如果没有 FlowLayout 会怎样?
如果 UICollectionView
没有 UICollectionViewLayout
,它就不知道怎么摆放 cell 。
你会看到:
-
cell 无法显示,因为没有布局信息
-
即使有数据源,collectionView 也无法渲染
所以,UICollectionView
必须绑定一个 Layout 对象 。
如果你不想用 UICollectionViewFlowLayout
,也可以自己写 UICollectionViewLayout
的子类,完全控制 cell 的位置和大小。
5. 灵活性对比
方案 | 适用场景 |
---|---|
UICollectionViewFlowLayout |
普通的网格、列表、瀑布流(稍微改造) |
UICollectionViewCompositionalLayout (iOS 13+) |
复杂布局(比如 App Store、新闻类 App) |
自定义 UICollectionViewLayout |
特殊布局(环形、3D 卡片、CoverFlow 效果) |
✅ 总结
-
UICollectionView
是数据容器,负责 cell 管理和交互。 -
UICollectionViewLayout
是策略类,负责计算 cell 的位置和大小。 -
UICollectionViewFlowLayout
是默认布局(网格/列表),所以常常一起使用。 -
分离布局的好处是:高度解耦、灵活切换、支持自定义。
6. 代码示例
Swift
import UIKit
class ViewController: UIViewController {
private var collectionView: UICollectionView!
private var data = Array(1...20).map { "Item \($0)" }
override func viewDidLoad() {
super.viewDidLoad()
//1.创建自定义布局
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 100)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
//2.初始化collectionView
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .systemGroupedBackground
collectionView.dataSource = self;
collectionView.delegate = self;
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(collectionView)
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
cell.configure(with: data[indexPath.row])
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected: \(data[indexPath.item])")
}
}
class CustomCell: UICollectionViewCell {
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setUI(){
contentView.backgroundColor = .systemBlue
contentView.layer.cornerRadius = 10
label.textAlignment = .center
label.textColor = .white
label.font = .boldSystemFont(ofSize: 18)
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
])
}
func configure(with text: String) {
label.text = text
}
}
