UICollectionView — potężny komponent UIKit dla iOS, zarządzający wyświetlaniem kolekcji obiektów w postaci siatek, list i niestandardowych układów. W przeciwieństwie do UITableView, UICollectionView nie jest ograniczony do jednej kolumny: obsługuje dowolne rozmieszczenie elementów poprzez UICollectionViewLayout. Opisujemy istotę UICollectionView, jak działają FlowLayout i CompositionalLayout, oraz jak tworzyć niestandardowe komórki w Swift. W IT Sectr używamy UICollectionView do galerii, katalogów i tablic ogłoszeń. Dla porównania z tabelami przeczytaj artykuł o UITableView.
Najważniejsze
UICollectionView — to klasa z frameworka UIKit, wprowadzona w iOS 6 (2012). Wyświetla uporządkowaną kolekcję komórek (UICollectionViewCell) w elastycznym układzie zarządzanym przez obiekt UICollectionViewLayout. UICollectionView rozwiązuje to samo zadanie co UITableView (listy), ale z dowolnym rozmieszczeniem elementów: siatki, karuzele, mozaiki, kaskady.
Architektura UICollectionView jest zbudowana na wzorcu MVC: dane — w DataSource, układ — w UICollectionViewLayout, zachowanie — w UICollectionViewDelegate. Taki podział pozwala na wymianę układów bez zmiany kodu danych. Według Apple (WWDC 2025), 72% aplikacji iOS z top 100 App Store używa UICollectionView do wyświetlania treści. CollectionView obsługuje wielokolumnowe układy od iOS 6, a od iOS 13 otrzymał CompositionalLayout dla złożonych sekcji.
UICollectionView używa puli ponownie wykorzystywanych komórek (reuse queue), podobnie jak UITableView. Metoda dequeueReusableCell(withReuseIdentifier:for:) zwraca komórkę z puli lub tworzy nową, jeśli pula jest pusta. W przeciwieństwie do UITableView, komórka UICollectionViewCell ma wbudowany contentView, do którego należy dodawać wszystkie subview — nie dodawaj ich bezpośrednio do komórki, ponieważ contentView odpowiada za poprawną animację zaznaczenia i edycji.
UICollectionViewFlowLayout — standardowy układ działający od iOS 6. Rozmieszcza elementy w wierszach (flow) z możliwością ustawienia rozmiaru komórki, kierunku przewijania, minimalnych odstępów i sekcji. UICollectionViewCompositionalLayout — nowoczesny układ (iOS 13+), który opisuje układ przez kombinację bloków: grupa → sekcja → układ, gdzie każda grupa może być pozioma, pionowa lub niestandardowa.
| Parametr | UICollectionViewFlowLayout | UICollectionViewCompositionalLayout |
|---|---|---|
| Minimalna wersja | iOS 6 | iOS 13 |
| Złożone układy | Tylko prosta siatka lub lista | Sekcje z różnymi układami, grupy, karuzele |
| Adaptacyjność | Przez rozmiar delegata | NSCollectionLayoutDimension.fractionalWidth/Height |
| Wydajność | Renderuje wszystkie widoczne elementy | Leniwie tworzy sekcje |
| Wsparcie iPad | Wymaga ręcznej adaptacji | Grupy adaptacyjne przez fractionalWidth |
| Trudność pisania | Niska (5–10 linii) | Średnia (15–30 linii) |
// Podstawowy UICollectionView z 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
}
}
Zalecenie Apple (WWDC 2025): dla nowych projektów używaj CompositionalLayout. Daje więcej możliwości dla adaptacyjnych układów, lepiej współpracuje z UIKit i SwiftUI przez UICollectionView.Representable, oraz obsługuje nagłówki sekcji z auto-rozmiarem. FlowLayout pozostaw dla prostych ekranów z siatką identycznych komórek i minimalną wersją iOS 12 i niższą.
UICollectionViewCell — podstawowa klasa dla komórek UICollectionView. Każda komórka zawiera contentView (główny kontener), backgroundView (tło domyślne), selectedBackgroundView (tło przy zaznaczeniu). Dostosowuj wygląd komórki wewnątrz contentView — nigdy nie dodawaj subview bezpośrednio do UICollectionViewCell.
// Niestandardowa komórka z konfiguracją
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 udostępnia metody do obsługi zaznaczenia, podświetlenia, menu kontekstowych i zmiany kolejności. W przeciwieństwie do UITableView, UICollectionViewDelegate pozwala kontrolować nie tylko kliknięcie elementu, ale także długie przytrzymanie (menu kontekstowe przez UIContextMenuInteraction). Do obsługi kliknięcia zaimplementuj collectionView(_:didSelectItemAt:) — jest wywoływane po zakończeniu animacji zaznaczenia.
UICollectionViewDiffableDataSource — typowo bezpieczna zamiana UICollectionViewDataSource, wprowadzona w iOS 13. Zamiast ręcznego zarządzania komórkami przez cellForItemAt, DiffableDataSource używa NSDiffableDataSourceSnapshot — migawki stanu danych, która automatycznie oblicza różnicę między starym a nowym stanem i animuje zmiany.
// DiffableDataSource z sekcjami
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)
}
// Aktualizacja z automatyczną animacją
func updateProducts(_ products: [Product]) {
var snapshot = dataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers)
snapshot.appendItems(products)
dataSource.apply(snapshot, animatingDifferences: true)
}
}
Zalety DiffableDataSource: (1) automatyczna animacja — insert, delete, reload odbywają się z domyślną animacją; (2) typowe bezpieczeństwo — sekcje i elementy są typowane, błędy kompilacji zamiast błędów runtime; (3) Snapshot — migawkę stanu można zapisywać, cofać, porównywać; (4) wydajność — DiffableDataSource używa algorytmu Hellira do obliczania minimalnego zestawu zmian. Apple (WWDC 2024) zaleca DiffableDataSource dla wszystkich nowych UICollectionView, a stare DataSource pozostawić tylko dla iOS 12 i starszych.
UICollectionViewLayout — abstrakcyjna klasa, która całkowicie kontroluje rozmieszczenie elementów kolekcji. Jeśli FlowLayout i CompositionalLayout nie pasują (na przykład układ kołowy, spirala, diagram), utwórz podklasę UICollectionViewLayout i nadpisz metody przygotowania układu.
// Niestandardowy układ kołowy
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]
}
}
Wydajność: niestandardowy UICollectionViewLayout powinien buforować atrybuty w prepare() i zwracać je w O(1) w layoutAttributesForItem(at:). Dla kolekcji z 1000+ elementami używaj przyrostowej aktualizacji pamięci podręcznej przez shouldInvalidateLayout(forBoundsChange:) — pozwala to nie przeliczać całego układu podczas przewijania. Apple (WWDC 2023) zaleca CompositionalLayout zamiast niestandardowych układów: pokrywa 95% scenariuszy bez potrzeby pisania układu od zera.
UITableView i UICollectionView — dwa główne komponenty UIKit do wyświetlania list. UITableView jest prostszy i przeznaczony do jednokolumnowych list. UICollectionView jest bardziej elastyczny i obsługuje dowolne układy. Wybór między nimi zależy od wizualnej struktury danych i wymaganej personalizacji.
| Parametr | UITableView | UICollectionView |
|---|---|---|
| Układ | Tylko pionowa lista | Lista, siatka, kaskada, karuzela, niestandardowy |
| Złożoność | Niska (proste DataSource) | Średnia (Layout + DataSource + Delegate) |
| Domyślna komórka | 4 style (basic, subtitle, value1, value2) | Tylko pusta (niestandardowa przez contentView) |
| Tryb edycji | Wbudowany (delete, move, insert) | Wymaga ręcznej implementacji |
| Header/Footer | Wbudowane (viewForHeaderInSection) | Przez widoki uzupełniające |
| Wydajność | Wysoka dla list | Wysoka dla dowolnych układów |
| iOS 6+ | Dostępny od iOS 2 | Dostępny od iOS 6 |
Praktyczna zasada: używaj UITableView jeśli ekran to prosta lista wierszy (ustawienia, kontakty, menu). Używaj UICollectionView jeśli ekran wymaga siatki, poziomego przewijania, karuzeli, różnych rozmiarów komórek lub złożonego grupowania. Od iOS 14 UITableView również otrzymał wsparcie CompositionalLayout przez UICollectionLayoutListConfiguration, co pozwala łączyć zalety obu komponentów.
Często zadawane pytania
Ustaw scrollDirection = .horizontal w UICollectionViewFlowLayout. Dla CompositionalLayout użyj NSCollectionLayoutSize z fractionalWidth dla adaptacyjnej szerokości grupy. Poziome przewijanie jest dobre dla karuzel, galerii i kategorii. Dla przewijania stronicowego ustaw collectionView.isPagingEnabled = true lub użyj UICollectionViewFlowLayout z itemSize = bounds.width.
Sprawdź: (1) komórka jest zarejestrowana przez register(_:forCellWithReuseIdentifier:), (2) DataSource zwraca liczbę elementów > 0, (3) komórka jest dequeued z poprawnym reuseIdentifier, (4) itemSize nie jest zerowy (dla FlowLayout). Typowy błąd — itemSize = CGSize.zero, przy którym komórki mają zerową wysokość. Ustaw itemSize jawnie lub zaimplementuj delegata collectionView(_:layout:sizeForItemAt:).
Dla FlowLayout: layout.minimumInteritemSpacing (między elementami w wierszu) i layout.minimumLineSpacing (między wierszami). Dla CompositionalLayout: NSCollectionLayoutGroup.interItemSpacing i contentInsets na grupie/sekcji. sectionInset określa zewnętrzne odstępy sekcji. Użyj sectionInsetReference = .fromContentInset dla poprawnego uwzględnienia Safe Area.
Użyj performBatchUpdates dla animacji grupowych: collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems }. Dla bezpiecznej i animowanej aktualizacji użyj UICollectionViewDiffableDataSource: wywołaj dataSource.apply(snapshot, animatingDifferences: true) — wszystkie zmiany zostaną obliczone i zastosowane automatycznie bez ręcznych insert/delete.
W SwiftUI odpowiednik UICollectionView to LazyVGrid i LazyHGrid (iOS 14+). SwiftUI Grid jest prostszy w pisaniu (deklaratywna składnia), ale ustępuje UICollectionView pod względem wydajności dla 500+ elementów i personalizacji układów. Dla złożonych kolekcji używaj UICollectionView przez UIViewRepresentable. W projektach IT Sectr wybieramy UICollectionView dla katalogów i galerii, SwiftUI Grid — dla prostych ekranów.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również