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) |
| Edit режим | Уграђени (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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође