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, към който трябва да се добавят всички subview — не ги добавяйте директно към клетката, защото 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 — никога не добавяйте subview директно към 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 позволява контрол не само на кликване върху елемент, но и на продължително натискане (контекстно меню чрез 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 грешки; (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) |
| Режим на редактиране | Вграден (delete, move, insert) | Изисква ръчна имплементация |
| Header/Footer | Вградени (viewForHeaderInSection) | Чрез допълнителни изгледи |
| Производителност | Висока за списъци | Висока за всякакви оформления |
| 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също