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 |
| Продуктивність | Рендерить всі видимі елементи | Ліниве створення секцій |
| Підтримка 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
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 — типобезпечна заміна 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% сценаріїв без необхідності писати макет з нуля.
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) комірка дек'юнута з правильним 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.