UICollectionView — 本质、集合和FlowLayout在iOS中

作者: IT Sectr 发布日期: 2026-02-23 阅读时间: 9 分钟

UICollectionView — 一个强大的iOS UIKit组件,用于以网格、列表和自定义布局的形式管理对象集合的显示。与UITableView不同,UICollectionView不局限于单列:它通过UICollectionViewLayout支持元素的任意排列。我们讲解UICollectionView的本质、FlowLayout和CompositionalLayout的工作原理,以及如何在Swift中创建自定义单元格。在IT Sectr,我们将UICollectionView用于画廊、目录和公告板。与表格比较,请阅读关于UITableView的文章

要点

  • UICollectionView — UIKit组件,用于以网格、列表和任意布局的形式显示集合。
  • UICollectionViewFlowLayout — 标准布局,支持方向、单元格大小和间距。
  • UICollectionViewCompositionalLayout — 现代布局(iOS 13+),用于包含块、轮播和组的复杂部分。
  • UICollectionViewDiffableDataSource — 类型安全的数据源,可自动计算更改(iOS 13+)。
  • UICollectionViewCell — 基础单元格,内置backgroundView、selectedBackgroundView和contentView。

什么是iOS中的UICollectionView?

UICollectionView — 来自UIKit框架的一个类,于iOS 6(2012年)引入。它在由UICollectionViewLayout对象管理的灵活布局中显示有序的单元格(UICollectionViewCell)集合。UICollectionView解决了与UITableView(列表)相同的任务,但具有元素的任意排列:网格、轮播、拼贴、瀑布流。

架构 UICollectionView基于MVC模式构建:数据 — 在DataSource中,布局 — 在UICollectionViewLayout中,行为 — 在UICollectionViewDelegate中。这种分离允许在不更改数据代码的情况下切换布局。根据Apple(WWDC 2025),App Store前100名中72%的iOS应用使用UICollectionView来显示内容。CollectionView从iOS 6开始支持多列布局,从iOS 13开始获得了用于复杂部分的CompositionalLayout。

单元格的生命周期

UICollectionView使用可重用单元格池(reuse queue),类似于UITableView。dequeueReusableCell(withReuseIdentifier:for:)方法从池中返回一个单元格,如果池为空则创建一个新单元格。与UITableView不同,UICollectionViewCell有一个内置的contentView,所有子视图都应添加到其中 — 不要直接将它们添加到单元格,因为contentView负责正确的选择动画和编辑动画。

FlowLayout与CompositionalLayout:比较

UICollectionViewFlowLayout — 自iOS 6起工作的标准布局。它将元素按行(flow)排列,可设置单元格大小、滚动方向、最小间距和部分。UICollectionViewCompositionalLayout — 现代布局(iOS 13+),通过块的组合描述布局:组 → 部分 → 布局,其中每个组可以是水平、垂直或自定义的。

参数UICollectionViewFlowLayoutUICollectionViewCompositionalLayout
最低版本iOS 6iOS 13
复杂布局仅简单网格或列表不同布局的部分、组、轮播
适应性通过委托大小NSCollectionLayoutDimension.fractionalWidth/Height
性能渲染所有可见元素延迟创建部分
iPad支持需要手动适配通过fractionalWidth的自适应组
编写复杂度低(5–10行)中(15–30行)
swift
// 带有FlowLayout的基本UICollectionView
class ViewController: UIViewController {

    private var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        layout.minimumInteritemSpacing = 8
        layout.minimumLineSpacing = 12
        layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
        layout.itemSize = CGSize(width: view.frame.width - 32, height: 100)

        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        view.addSubview(collectionView)
    }
}

// DataSource
extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView,
                         numberOfItemsInSection section: Int) -> Int { 20 }

    func collectionView(_ collectionView: UICollectionView,
                         cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        as! CustomCell
        cell.configure(with: "Item \(indexPath.row)")
        return cell
    }
}

Apple建议(WWDC 2025):对于新项目,使用CompositionalLayout。它为自适应布局提供了更多可能性,通过UICollectionView.Representable与UIKit和SwiftUI更好地协作,并支持自动大小的部分标题。将FlowLayout留给具有相同单元格网格和最低版本iOS 12及以下的简单屏幕。

UICollectionViewCell和UICollectionViewDelegate

UICollectionViewCell — UICollectionView单元格的基类。每个单元格包含contentView(主容器)、backgroundView(默认背景)、selectedBackgroundView(选中时背景)。在contentView内部调整单元格的外观 — 切勿直接将子视图添加到UICollectionViewCell。

swift
// 带配置的自定义单元格
class ProductCell: UICollectionViewCell {

    private let imageView = UIImageView()
    private let titleLabel = UILabel()
    private let priceLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupViews() {
        contentView.addSubview(imageView)
        contentView.addSubview(titleLabel)
        contentView.addSubview(priceLabel)

        // Auto Layout constraints
        imageView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
            titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
            priceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
            priceLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8)
        ])
    }

    func configure(with product: Product) {
        imageView.image = UIImage(named: product.imageName)
        titleLabel.text = product.title
        priceLabel.text = product.price
    }
}

UICollectionViewDelegate提供处理选择、高亮、上下文菜单和重新排序的方法。与UITableView不同,UICollectionViewDelegate不仅可以控制元素的点击,还可以控制长按(通过UIContextMenuInteraction的上下文菜单)。要处理点击,请实现collectionView(_:didSelectItemAt:) — 在选择动画完成后调用。

UICollectionViewDiffableDataSource:现代数据源

UICollectionViewDiffableDataSource — 类型安全的UICollectionViewDataSource替代品,于iOS 13中引入。无需通过cellForItemAt手动管理单元格,DiffableDataSource使用NSDiffableDataSourceSnapshot — 数据状态的快照,自动计算新旧状态之间的差异并动画显示更改。

swift
// 带部分的DiffableDataSource
enum Section {
    case featured, recommended, categories
}

class ModernDataSource: UIViewController {

    private var dataSource: UICollectionViewDiffableDataSource<Section, Product>!

    override func viewDidLoad() {
        super.viewDidLoad()
        configureDataSource()
        applyInitialSnapshot()
    }

    private func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<ProductCell, Product> { cell, _, product in
            cell.configure(with: product)
        }

        dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
            collectionView, indexPath, product in
            collectionView.dequeueConfiguredReusableCell(
                using: cellRegistration, for: indexPath, item: product
            )
        }
    }

    private func applyInitialSnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Product>()
        snapshot.appendSections([.featured, .recommended, .categories])
        snapshot.appendItems(featuredProducts, toSection: .featured)
        snapshot.appendItems(recommendedProducts, toSection: .recommended)
        snapshot.appendItems(categories, toSection: .categories)
        dataSource.apply(snapshot, animatingDifferences: true)
    }

    // 自动动画更新
    func updateProducts(_ products: [Product]) {
        var snapshot = dataSource.snapshot()
        snapshot.deleteItems(snapshot.itemIdentifiers)
        snapshot.appendItems(products)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

DiffableDataSource的优势:(1)自动动画 — insert、delete、reload以默认动画进行;(2)类型安全 — 部分和元素是类型化的,编译错误而非运行时错误;(3)Snapshot — 状态快照可以保存、撤销、比较;(4)性能 — DiffableDataSource使用Hellir算法计算最小更改集。Apple(WWDC 2024)推荐对所有新的UICollectionView使用DiffableDataSource,旧的数据源仅保留给iOS 12及更早版本。

通过UICollectionViewLayout创建自定义布局

UICollectionViewLayout — 抽象类,完全控制集合元素的排列。如果FlowLayout和CompositionalLayout不适用(例如圆形布局、螺旋、图表),创建UICollectionViewLayout的子类并重写布局准备方法。

swift
// 自定义圆形布局
class CircularLayout: UICollectionViewLayout {

    private var attributesCache: [UICollectionViewLayoutAttributes] = []

    private let radius: CGFloat = 120

    override func prepare() {
        super.prepare()
        attributesCache.removeAll()

        guard let collectionView else { return }
        let count = collectionView.numberOfItems(inSection: 0)
        let center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY)

        for i in 0..<count {
            let indexPath = IndexPath(item: i, section: 0)
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            let angle = (CGFloat(i) / CGFloat(count)) * 2 * .pi

            attributes.center = CGPoint(
                x: center.x + radius * cos(angle),
                y: center.y + radius * sin(angle)
            )
            attributes.size = CGSize(width: 60, height: 60)
            attributesCache.append(attributes)
        }
    }

    override var collectionViewContentSize: CGSize {
        collectionView?.bounds.size ?? .zero
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        attributesCache.filter { $0.frame.intersects(rect) }
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        attributesCache[indexPath.item]
    }
}

性能:自定义UICollectionViewLayout应在prepare()中缓存属性,并在layoutAttributesForItem(at:)中以O(1)返回它们。对于1000+元素的集合,通过shouldInvalidateLayout(forBoundsChange:)使用增量缓存更新 — 这允许在滚动时不必重新计算整个布局。Apple(WWDC 2023)推荐CompositionalLayout而非自定义布局:它覆盖95%的场景,无需从头编写布局。

UICollectionView与UITableView:何时选择什么

UITableViewUICollectionView — 用于显示列表的两个主要UIKit组件。UITableView更简单,专为单列列表设计。UICollectionView更灵活,支持任何布局。两者之间的选择取决于数据的视觉结构和所需的定制化。

参数UITableViewUICollectionView
布局仅垂直列表列表、网格、瀑布流、轮播、自定义
复杂度低(简单数据源)中(布局 + 数据源 + 委托)
默认单元格4种样式(basic、subtitle、value1、value2)仅空(通过contentView自定义)
编辑模式内置(delete、move、insert)需要手动实现
页眉/页脚内置(viewForHeaderInSection)通过补充视图
性能列表高任何布局都高
iOS 6+从iOS 2可用从iOS 6可用

实践规则:如果屏幕是简单的行列表(设置、联系人、菜单),使用UITableView。如果屏幕需要网格、水平滚动、轮播、不同单元格大小或复杂分组,使用UICollectionView。从iOS 14开始,UITableView也通过UICollectionLayoutListConfiguration获得了CompositionalLayout支持,这使得可以结合两个组件的优势。

常见问题

如何创建具有水平滚动的UICollectionView?

在UICollectionViewFlowLayout中设置scrollDirection = .horizontal。对于CompositionalLayout,使用带有fractionalWidth的NSCollectionLayoutSize来实现组的自适应宽度。水平滚动适用于轮播、画廊和分类。对于分页滚动,设置collectionView.isPagingEnabled = true或使用itemSize = bounds.width的UICollectionViewFlowLayout。

为什么UICollectionView不显示单元格?

检查:(1)单元格已通过register(_:forCellWithReuseIdentifier:)注册,(2)DataSource返回的元素数量 > 0,(3)单元格使用正确的reuseIdentifier出队,(4)itemSize不为零(对于FlowLayout)。典型错误 — itemSize = CGSize.zero,导致单元格高度为零。显式设置itemSize或实现委托collectionView(_:layout:sizeForItemAt:)

如何在UICollectionView单元格之间添加间距?

对于FlowLayout:layout.minimumInteritemSpacing(行内元素之间)和layout.minimumLineSpacing(行之间)。对于CompositionalLayout:NSCollectionLayoutGroup.interItemSpacing和组/部分上的contentInsets。sectionInset设置部分的外部间距。使用sectionInsetReference = .fromContentInset来正确考虑Safe Area。

如何在不重新加载的情况下更新UICollectionView?

使用performBatchUpdates进行组动画:collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems }。对于安全和动画更新,使用UICollectionViewDiffableDataSource:调用dataSource.apply(snapshot, animatingDifferences: true) — 所有更改将自动计算并应用,无需手动insert/delete。

UICollectionView与SwiftUI中的CollectionView?

在SwiftUI中,UICollectionView的对应物是LazyVGridLazyHGrid(iOS 14+)。SwiftUI Grid在编写上更简单(声明式语法),但在500+元素和布局自定义方面不如UICollectionView。对于复杂集合,通过UIViewRepresentable使用UICollectionView。在IT Sectr项目中,我们为目录和画廊选择UICollectionView,为简单屏幕选择SwiftUI Grid。

总结

  • UICollectionView — 用于网格、列表、轮播和自定义布局的灵活UIKit组件(iOS 6+)。
  • FlowLayout适用于简单网格;CompositionalLayout(iOS 13+)适用于具有不同布局的复杂部分。
  • UICollectionViewCell对所有子视图使用contentView — 不要直接将它们添加到单元格。
  • UICollectionViewDiffableDataSource — 现代类型安全的数据源,具有自动更改动画。
  • 自定义UICollectionViewLayout允许创建任意布局(圆形、螺旋、图表)。
  • App Store前100名中72%的iOS应用使用UICollectionView来显示内容。
  • 对于简单列表使用UITableView;对于其他一切 — UICollectionView。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读