UICollectionView — суть, коллекции и FlowLayout в iOS

Автор: IT Sectr Опубликовано: 2026-02-23 Время чтения: 9 мин

UICollectionView — мощный компонент UIKit для iOS, управляющий отображением коллекций объектов в виде сеток, списков и кастомных макетов. В отличие от UITableView, UICollectionView не ограничен одной колонкой: он поддерживает произвольное расположение элементов через UICollectionViewLayout. Рассказываем, в чём суть UICollectionView, как работают FlowLayout и CompositionalLayout, и как создавать кастомные ячейки на Swift. В IT Sectr мы используем UICollectionView для галерей, каталогов и досок объявлений. Для сравнения с таблицами читайте статью о UITableView.

Главное

  • UICollectionView — компонент UIKit для отображения коллекций в виде сеток, списков и произвольных макетов.
  • UICollectionViewFlowLayout — стандартный макет с поддержкой направления, размеров ячеек и интервалов.
  • UICollectionViewCompositionalLayout — современный макет (iOS 13+) для сложных секций с блоками, каруселями и группами.
  • UICollectionViewDiffableDataSource — типобезопасный DataSource с автоматическим расчётом изменений (iOS 13+).
  • UICollectionViewCell — базовая ячейка с встроенными backgroundView, selectedBackgroundView и contentView.

Что такое UICollectionView в iOS?

UICollectionView — это класс из фреймворка UIKit, представленный в iOS 6 (2012). Он отображает упорядоченную коллекцию ячеек (UICollectionViewCell) в гибком макете, управляемом объектом UICollectionViewLayout. UICollectionView решает ту же задачу, что и UITableView (списки), но с произвольным расположением элементов: сетки, карусели, мозаики, каскады.

Архитектура UICollectionView построена на паттерне MVC: данные — в DataSource, макет — в UICollectionViewLayout, поведение — в UICollectionViewDelegate. Такое разделение позволяет заменять макеты без изменения кода данных. По данным Apple (WWDC 2025), 72% iOS-приложений из топ-100 App Store используют UICollectionView для отображения контента. CollectionView поддерживает многоколоночные макеты с iOS 6, а с iOS 13 получил CompositionalLayout для сложных секций.

Жизненный цикл ячейки

UICollectionView использует пул переиспользуемых ячеек (reuse queue), аналогично UITableView. Метод dequeueReusableCell(withReuseIdentifier:for:) возвращает ячейку из пула или создаёт новую, если пул пуст. В отличие от UITableView, ячейка UICollectionViewCell имеет встроенный contentView, в который нужно добавлять все сабвью — не добавляйте их напрямую в ячейку, так как contentView отвечает за корректную анимацию выделения и редактирования.

FlowLayout vs CompositionalLayout: сравнение

UICollectionViewFlowLayout — стандартный макет, работающий с iOS 6. Он располагает элементы по строкам (flow) с возможностью настройки размера ячейки, направления скролла, минимальных интервалов и секций. UICollectionViewCompositionalLayout — современный макет (iOS 13+), который описывает макет через комбинацию блоков: группа → секция → макет, где каждая группа может быть горизонтальной, вертикальной или кастомной.

ПараметрUICollectionViewFlowLayoutUICollectionViewCompositionalLayout
Минимальная версияiOS 6iOS 13
Сложные макетыТолько простая сетка или списокСекции с разными макетами, группы, карусели
АдаптивностьЧерез размер делегатаNSCollectionLayoutDimension.fractionalWidth/Height
ПроизводительностьРендерит все visible элементыЛенивое создание секций
Поддержка iPadТребует ручной адаптацииАдаптивные группы через fractionalWidth
Сложность написанияНизкая (5–10 строк)Средняя (15–30 строк)
swift
// Базовый UICollectionView с FlowLayout
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. Он даёт больше возможностей для адаптивных макетов, лучше работает с UIKit и SwiftUI через UICollectionView.Representable, и поддерживает секционные заголовки с авто-размером. 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 позволяет контролировать не только нажатие на элемент, но и длительное нажатие (context menu через UIContextMenuInteraction). Для обработки нажатия реализуйте collectionView(_:didSelectItemAt:) — он вызывается при завершении анимации выделения.

UICollectionViewDiffableDataSource: современный DataSource

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) типобезопасность — секции и элементы типизированы, ошибки компиляции вместо runtime crash; (3) Snapshot — снимок состояния можно сохранять, отменять, сравнивать; (4) производительность — DiffableDataSource использует алгоритм Хеллира для расчёта минимального набора изменений. Apple (WWDC 2024) рекомендует DiffableDataSource для всех новых UICollectionView, а старый DataSource оставить только для 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() и возвращать их за O(1) в layoutAttributesForItem(at:). Для коллекций с 1000+ элементов используйте инкрементальное обновление кэша через shouldInvalidateLayout(forBoundsChange:) — это позволяет не пересчитывать весь макет при скролле. Apple (WWDC 2023) рекомендует CompositionalLayout вместо кастомных макетов: он покрывает 95% сценариев без необходимости писать layout с нуля.

UICollectionView vs UITableView: когда что выбирать

UITableView и UICollectionView — два основных компонента UIKit для отображения списков. UITableView проще и предназначен для одноколоночных списков. UICollectionView гибче и поддерживает любые макеты. Выбор между ними зависит от визуальной структуры данных и требуемой кастомизации.

ПараметрUITableViewUICollectionView
МакетТолько вертикальный списокСписок, сетка, каскад, карусель, кастомный
СложностьНизкая (простой DataSource)Средняя (Layout + DataSource + Delegate)
Ячейка по умолчанию4 стиля (basic, subtitle, value1, value2)Только пустая (кастом через contentView)
Edit modeВстроенный (delete, move, insert)Требует ручной реализации
Header/FooterВстроенные (viewForHeaderInSection)Через supplementary views
ПроизводительностьВысокая для списковВысокая для любых макетов
iOS 6+Доступен с iOS 2Доступен с iOS 6

Практическое правило: используйте UITableView если экран — это простой список строк (настройки, контакты, меню). Используйте UICollectionView если экран требует сетки, горизонтального скролла, карусели, разных размеров ячеек или сложной группировки. С iOS 14 UITableView также получил поддержку CompositionalLayout через UICollectionLayoutListConfiguration, что позволяет комбинировать преимущества обоих компонентов.

Часто задаваемые вопросы

Как сделать UICollectionView с горизонтальным скроллом?

Установите scrollDirection = .horizontal в UICollectionViewFlowLayout. Для CompositionalLayout используйте NSCollectionLayoutSize с fractionalWidth для адаптивной ширины группы. Горизонтальный скролл хорош для каруселей, галерей и категорий. Для постраничного скролла установите collectionView.isPagingEnabled = true или используйте UICollectionViewFlowLayout с itemSize = bounds.width.

Почему UICollectionView не отображает ячейки?

Проверьте: (1) ячейка зарегистрирована через register(_:forCellWithReuseIdentifier:), (2) DataSource возвращает количество элементов > 0, (3) ячейка dequeued с правильным 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 vs CollectionView в SwiftUI?

В SwiftUI аналог UICollectionView — LazyVGrid и LazyHGrid (iOS 14+). SwiftUI Grid проще в написании (декларативный синтаксис), но уступает UICollectionView в производительности для 500+ элементов и кастомизации макетов. Для сложных коллекций используйте UICollectionView через UIViewRepresentable. В проектах IT Sectr мы выбираем UICollectionView для каталогов и галерей, SwiftUI Grid — для простых экранов.

Итоги

  • UICollectionView — гибкий компонент UIKit для сеток, списков, каруселей и кастомных макетов (iOS 6+).
  • FlowLayout подходит для простых сеток; CompositionalLayout (iOS 13+) — для сложных секций с разными макетами.
  • UICollectionViewCell использует contentView для всех сабвью — не добавляйте их напрямую в ячейку.
  • UICollectionViewDiffableDataSource — современный типобезопасный DataSource с автоматической анимацией изменений.
  • Кастомный UICollectionViewLayout позволяет создавать произвольные макеты (круг, спираль, диаграмма).
  • 72% iOS-приложений топ-100 App Store используют UICollectionView для отображения контента.
  • Для простых списков используйте UITableView; для всего остального — UICollectionView.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также