UICollectionView — мощный компонент UIKit для iOS, управляющий отображением коллекций объектов в виде сеток, списков и кастомных макетов. В отличие от UITableView, UICollectionView не ограничен одной колонкой: он поддерживает произвольное расположение элементов через UICollectionViewLayout. Рассказываем, в чём суть UICollectionView, как работают FlowLayout и CompositionalLayout, и как создавать кастомные ячейки на Swift. В IT Sectr мы используем UICollectionView для галерей, каталогов и досок объявлений. Для сравнения с таблицами читайте статью о UITableView.
Главное
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 отвечает за корректную анимацию выделения и редактирования.
UICollectionViewFlowLayout — стандартный макет, работающий с iOS 6. Он располагает элементы по строкам (flow) с возможностью настройки размера ячейки, направления скролла, минимальных интервалов и секций. UICollectionViewCompositionalLayout — современный макет (iOS 13+), который описывает макет через комбинацию блоков: группа → секция → макет, где каждая группа может быть горизонтальной, вертикальной или кастомной.
| Параметр | UICollectionViewFlowLayout | UICollectionViewCompositionalLayout |
|---|---|---|
| Минимальная версия | iOS 6 | iOS 13 |
| Сложные макеты | Только простая сетка или список | Секции с разными макетами, группы, карусели |
| Адаптивность | Через размер делегата | NSCollectionLayoutDimension.fractionalWidth/Height |
| Производительность | Рендерит все visible элементы | Ленивое создание секций |
| Поддержка iPad | Требует ручной адаптации | Адаптивные группы через fractionalWidth |
| Сложность написания | Низкая (5–10 строк) | Средняя (15–30 строк) |
// Базовый 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 — базовый класс для ячеек UICollectionView. Каждая ячейка содержит contentView (основной контейнер), backgroundView (фон по умолчанию), selectedBackgroundView (фон при выделении). Настраивайте внешний вид ячейки внутри contentView — никогда не добавляйте сабвью напрямую в UICollectionViewCell.
// Кастомная ячейка с конфигурацией
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 — типобезопасная замена UICollectionViewDataSource, представленная в iOS 13. Вместо ручного управления ячейками через cellForItemAt, DiffableDataSource использует NSDiffableDataSourceSnapshot — снимок состояния данных, который автоматически вычисляет разницу между старым и новым состоянием и анимирует изменения.
// 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 — абстрактный класс, который полностью контролирует расположение элементов коллекции. Если FlowLayout и CompositionalLayout не подходят (например, круговой макет, спираль, диаграмма), создайте подкласс UICollectionViewLayout и переопределите методы подготовки макета.
// Кастомный круговой макет
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 с нуля.
UITableView и UICollectionView — два основных компонента UIKit для отображения списков. UITableView проще и предназначен для одноколоночных списков. UICollectionView гибче и поддерживает любые макеты. Выбор между ними зависит от визуальной структуры данных и требуемой кастомизации.
| Параметр | UITableView | UICollectionView |
|---|---|---|
| Макет | Только вертикальный список | Список, сетка, каскад, карусель, кастомный |
| Сложность | Низкая (простой 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, что позволяет комбинировать преимущества обоих компонентов.
Часто задаваемые вопросы
Установите scrollDirection = .horizontal в UICollectionViewFlowLayout. Для CompositionalLayout используйте NSCollectionLayoutSize с fractionalWidth для адаптивной ширины группы. Горизонтальный скролл хорош для каруселей, галерей и категорий. Для постраничного скролла установите collectionView.isPagingEnabled = true или используйте UICollectionViewFlowLayout с itemSize = bounds.width.
Проверьте: (1) ячейка зарегистрирована через register(_:forCellWithReuseIdentifier:), (2) DataSource возвращает количество элементов > 0, (3) ячейка dequeued с правильным reuseIdentifier, (4) itemSize не нулевой (для FlowLayout). Типичная ошибка — itemSize = CGSize.zero, при котором ячейки имеют нулевую высоту. Установите itemSize явно или реализуйте делегат collectionView(_:layout:sizeForItemAt:).
Для FlowLayout: layout.minimumInteritemSpacing (между элементами в строке) и layout.minimumLineSpacing (между строками). Для CompositionalLayout: NSCollectionLayoutGroup.interItemSpacing и contentInsets на группе/секции. sectionInset задаёт внешние отступы секции. Используйте sectionInsetReference = .fromContentInset для корректного учёта Safe Area.
Используйте performBatchUpdates для групповых анимаций: collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems }. Для безопасного и анимированного обновления используйте UICollectionViewDiffableDataSource: вызовите dataSource.apply(snapshot, animatingDifferences: true) — все изменения просчитаются и применятся автоматически без ручных insert/delete.
В SwiftUI аналог UICollectionView — LazyVGrid и LazyHGrid (iOS 14+). SwiftUI Grid проще в написании (декларативный синтаксис), но уступает UICollectionView в производительности для 500+ элементов и кастомизации макетов. Для сложных коллекций используйте UICollectionView через UIViewRepresentable. В проектах IT Sectr мы выбираем UICollectionView для каталогов и галерей, SwiftUI Grid — для простых экранов.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.