UICollectionView — o componentă puternică UIKit pentru iOS care gestionează afișarea colecțiilor de obiecte sub formă de grile, liste și machete personalizate. Spre deosebire de UITableView, UICollectionView nu este limitat la o singură coloană: suportă amplasarea arbitrară a elementelor prin UICollectionViewLayout. Explicăm esența UICollectionView, cum funcționează FlowLayout și CompositionalLayout și cum să creați celule personalizate în Swift. În IT Sectr folosim UICollectionView pentru galerii, cataloage și panouri de anunțuri. Pentru comparație cu tabele, citiți articolul despre UITableView.
Principalele puncte
UICollectionView — este o clasă din framework-ul UIKit, introdusă în iOS 6 (2012). Afișează o colecție ordonată de celule (UICollectionViewCell) într-o machetă flexibilă gestionată de un obiect UICollectionViewLayout. UICollectionView rezolvă aceeași sarcină ca UITableView (liste), dar cu amplasarea arbitrară a elementelor: grile, carusele, mozaicuri, cascade.
Arhitectura UICollectionView este construită pe modelul MVC: datele — în DataSource, macheta — în UICollectionViewLayout, comportamentul — în UICollectionViewDelegate. Această separare permite schimbarea machetelor fără a modifica codul datelor. Potrivit Apple (WWDC 2025), 72% din aplicațiile iOS din top 100 App Store folosesc UICollectionView pentru afișarea conținutului. CollectionView suportă machete multi-coloană din iOS 6, iar din iOS 13 a primit CompositionalLayout pentru secțiuni complexe.
UICollectionView utilizează un pool de celule reutilizabile (reuse queue), similar cu UITableView. Metoda dequeueReusableCell(withReuseIdentifier:for:) returnează o celulă din pool sau creează una nouă dacă pool-ul este gol. Spre deosebire de UITableView, celula UICollectionViewCell are un contentView încorporat la care trebuie adăugate toate subview-urile — nu le adăugați direct în celulă, deoarece contentView este responsabil pentru animația corectă a selecției și editării.
UICollectionViewFlowLayout — machetă standard care funcționează din iOS 6. Aranjează elementele pe rânduri (flow) cu posibilitatea de a seta dimensiunea celulei, direcția de derulare, intervalele minime și secțiunile. UICollectionViewCompositionalLayout — machetă modernă (iOS 13+) care descrie macheta prin combinația de blocuri: grup → secțiune → machetă, unde fiecare grup poate fi orizontal, vertical sau personalizat.
| Parametru | UICollectionViewFlowLayout | UICollectionViewCompositionalLayout |
|---|---|---|
| Versiune minimă | iOS 6 | iOS 13 |
| Machete complexe | Doar grilă simplă sau listă | Secțiuni cu machete diferite, grupuri, carusele |
| Adaptabilitate | Prin dimensiunea delegatului | NSCollectionLayoutDimension.fractionalWidth/Height |
| Performanță | Randează toate elementele vizibile | Creare lentă a secțiunilor |
| Suport iPad | Necesită adaptare manuală | Grupuri adaptive prin fractionalWidth |
| Complexitate scriere | Redusă (5–10 linii) | Medie (15–30 linii) |
// UICollectionView de bază cu 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
}
}
Recomandarea Apple (WWDC 2025): pentru proiecte noi utilizați CompositionalLayout. Oferă mai multe posibilități pentru machete adaptive, funcționează mai bine cu UIKit și SwiftUI prin UICollectionView.Representable și suportă anteturi de secțiune cu dimensiune automată. Lăsați FlowLayout pentru ecrane simple cu o grilă de celule identice și versiunea minimă iOS 12 și mai jos.
UICollectionViewCell — clasa de bază pentru celulele UICollectionView. Fiecare celulă conține contentView (containerul principal), backgroundView (fundal implicit), selectedBackgroundView (fundal la selectare). Personalizați aspectul celulei în interiorul contentView — nu adăugați niciodată subview-uri direct în UICollectionViewCell.
// Celulă personalizată cu configurare
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 oferă metode pentru gestionarea selecției, evidențierii, meniurilor contextuale și reordonării. Spre deosebire de UITableView, UICollectionViewDelegate permite controlarea nu doar a clicului pe element, ci și a presării lungi (meniu contextual prin UIContextMenuInteraction). Pentru a gestiona clicul, implementați collectionView(_:didSelectItemAt:) — este apelat la finalizarea animației de selecție.
UICollectionViewDiffableDataSource — înlocuitor tip-securizat pentru UICollectionViewDataSource, introdus în iOS 13. În locul gestionării manuale a celulelor prin cellForItemAt, DiffableDataSource utilizează NSDiffableDataSourceSnapshot — o imagine a stării datelor care calculează automat diferența dintre starea veche și cea nouă și animează modificările.
// DiffableDataSource cu secțiuni
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)
}
// Actualizare cu animație automată
func updateProducts(_ products: [Product]) {
var snapshot = dataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers)
snapshot.appendItems(products)
dataSource.apply(snapshot, animatingDifferences: true)
}
}
Avantajele DiffableDataSource: (1) animație automată — insert, delete, reload se întâmplă cu animație implicită; (2) tip-securizare — secțiunile și elementele sunt tipizate, erori de compilare în loc de erori runtime; (3) Snapshot — imaginea stării poate fi salvată, anulată, comparată; (4) performanță — DiffableDataSource folosește algoritmul Hellir pentru calcularea setului minim de modificări. Apple (WWDC 2024) recomandă DiffableDataSource pentru toate UICollectionView-urile noi, iar DataSource-ul vechi să fie păstrat doar pentru iOS 12 și mai vechi.
UICollectionViewLayout — clasă abstractă care controlează complet aranjarea elementelor colecției. Dacă FlowLayout și CompositionalLayout nu se potrivesc (de exemplu, machetă circulară, spirală, diagramă), creați o subclasă a UICollectionViewLayout și suprascrieți metodele de pregătire a machetei.
// Machetă circulară personalizată
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]
}
}
Performanță: UICollectionViewLayout personalizat trebuie să cache-uiască atributele în prepare() și să le returneze în O(1) în layoutAttributesForItem(at:). Pentru colecții cu 1000+ elemente, utilizați actualizarea incrementală a cache-ului prin shouldInvalidateLayout(forBoundsChange:) — aceasta permite să nu recalculați întreaga machetă la derulare. Apple (WWDC 2023) recomandă CompositionalLayout în locul machetelor personalizate: acoperă 95% din scenarii fără a fi nevoie să scrieți o machetă de la zero.
UITableView și UICollectionView — două componente principale UIKit pentru afișarea listelor. UITableView este mai simplu și destinat listelor cu o singură coloană. UICollectionView este mai flexibil și suportă orice machete. Alegerea între ele depinde de structura vizuală a datelor și de personalizarea necesară.
| Parametru | UITableView | UICollectionView |
|---|---|---|
| Machetă | Doar listă verticală | Listă, grilă, cascadă, carusel, personalizată |
| Complexitate | Scăzută (DataSource simplu) | Medie (Layout + DataSource + Delegate) |
| Celulă implicită | 4 stiluri (basic, subtitle, value1, value2) | Doar goală (personalizată prin contentView) |
| Mod editare | Încorporat (delete, move, insert) | Necesită implementare manuală |
| Header/Footer | Încorporate (viewForHeaderInSection) | Prin vizualizări suplimentare |
| Performanță | Ridicată pentru liste | Ridicată pentru orice machete |
| iOS 6+ | Disponibil din iOS 2 | Disponibil din iOS 6 |
Regulă practică: utilizați UITableView dacă ecranul este o listă simplă de rânduri (setări, contacte, meniu). Utilizați UICollectionView dacă ecranul necesită o grilă, derulare orizontală, carusel, dimensiuni diferite ale celulelor sau grupare complexă. Din iOS 14, UITableView a primit și el suport CompositionalLayout prin UICollectionLayoutListConfiguration, ceea ce permite combinarea avantajelor ambelor componente.
Întrebări frecvente
Setați scrollDirection = .horizontal în UICollectionViewFlowLayout. Pentru CompositionalLayout utilizați NSCollectionLayoutSize cu fractionalWidth pentru lățimea adaptivă a grupului. Derularea orizontală este bună pentru carusele, galerii și categorii. Pentru derulare paginată, setați collectionView.isPagingEnabled = true sau utilizați UICollectionViewFlowLayout cu itemSize = bounds.width.
Verificați: (1) celula este înregistrată prin register(_:forCellWithReuseIdentifier:), (2) DataSource returnează numărul de elemente > 0, (3) celula este dequeued cu reuseIdentifier corect, (4) itemSize nu este zero (pentru FlowLayout). Eroare tipică — itemSize = CGSize.zero, caz în care celulele au înălțime zero. Setați itemSize explicit sau implementați delegatul collectionView(_:layout:sizeForItemAt:).
Pentru FlowLayout: layout.minimumInteritemSpacing (între elemente pe rând) și layout.minimumLineSpacing (între rânduri). Pentru CompositionalLayout: NSCollectionLayoutGroup.interItemSpacing și contentInsets pe grup/secțiune. sectionInset setează spațiile externe ale secțiunii. Utilizați sectionInsetReference = .fromContentInset pentru luarea corectă în considerare a Safe Area.
Utilizați performBatchUpdates pentru animații de grup: collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems }. Pentru o actualizare sigură și animată, utilizați UICollectionViewDiffableDataSource: apelați dataSource.apply(snapshot, animatingDifferences: true) — toate modificările vor fi calculate și aplicate automat fără insert/delete manuale.
În SwiftUI, analogul UICollectionView este LazyVGrid și LazyHGrid (iOS 14+). SwiftUI Grid este mai simplu de scris (sintaxă declarativă), dar este inferior UICollectionView în performanță pentru 500+ elemente și personalizarea machetelor. Pentru colecții complexe, utilizați UICollectionView prin UIViewRepresentable. În proiectele IT Sectr alegem UICollectionView pentru cataloage și galerii, SwiftUI Grid — pentru ecrane simple.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și