Introduction
Creating UICollectionViewCell programmatically gives you full control over layout, reuse, and dynamic styling. It is often a better fit than storyboard cells when your UI is generated from data or your team prefers code-first views. A robust implementation includes clean cell composition, registration, and predictable sizing behavior.
Core Sections
Build a reusable custom cell class
Start with a UICollectionViewCell subclass that owns its subviews and constraints. Keep configuration inside a dedicated method so reuse stays clean.
1import UIKit
2
3final class ProductCell: UICollectionViewCell {
4 static let reuseId = "ProductCell"
5
6 private let titleLabel: UILabel = {
7 let label = UILabel()
8 label.numberOfLines = 2
9 label.font = .preferredFont(forTextStyle: .headline)
10 label.translatesAutoresizingMaskIntoConstraints = false
11 return label
12 }()
13
14 override init(frame: CGRect) {
15 super.init(frame: frame)
16 contentView.backgroundColor = .secondarySystemBackground
17 contentView.layer.cornerRadius = 10
18 contentView.addSubview(titleLabel)
19
20 NSLayoutConstraint.activate([
21 titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
22 titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12),
23 titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12),
24 titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12)
25 ])
26 }
27
28 required init?(coder: NSCoder) {
29 fatalError("init(coder:) has not been implemented")
30 }
31
32 override func prepareForReuse() {
33 super.prepareForReuse()
34 titleLabel.text = nil
35 }
36
37 func configure(title: String) {
38 titleLabel.text = title
39 }
40}
Register cell and implement data source
Use UICollectionViewFlowLayout for straightforward grids. Register the class, then dequeue using the reuse identifier.
1import UIKit
2
3final class ProductsViewController: UIViewController, UICollectionViewDataSource {
4 private let items = ["Keyboard", "Mouse", "Monitor", "Dock", "Headphones"]
5
6 private lazy var collectionView: UICollectionView = {
7 let layout = UICollectionViewFlowLayout()
8 layout.minimumLineSpacing = 12
9 layout.minimumInteritemSpacing = 12
10 let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
11 cv.translatesAutoresizingMaskIntoConstraints = false
12 cv.backgroundColor = .systemBackground
13 cv.dataSource = self
14 cv.register(ProductCell.self, forCellWithReuseIdentifier: ProductCell.reuseId)
15 return cv
16 }()
17
18 override func viewDidLoad() {
19 super.viewDidLoad()
20 view.addSubview(collectionView)
21 NSLayoutConstraint.activate([
22 collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
23 collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
24 collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
25 collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
26 ])
27 }
28
29 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
30 items.count
31 }
32
33 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
34 guard let cell = collectionView.dequeueReusableCell(
35 withReuseIdentifier: ProductCell.reuseId,
36 for: indexPath
37 ) as? ProductCell else {
38 return UICollectionViewCell()
39 }
40 cell.configure(title: items[indexPath.item])
41 return cell
42 }
43}
Size cells predictably
Implement UICollectionViewDelegateFlowLayout for explicit sizing, or use self-sizing with estimated size for dynamic text. For a two-column grid:
1extension ProductsViewController: UICollectionViewDelegateFlowLayout {
2 func collectionView(_ collectionView: UICollectionView,
3 layout collectionViewLayout: UICollectionViewLayout,
4 sizeForItemAt indexPath: IndexPath) -> CGSize {
5 let width = (collectionView.bounds.width - 12) / 2
6 return CGSize(width: width, height: 100)
7 }
8}
Explicit sizing avoids many runtime surprises and is easy to test on different devices.
Production tips for maintainability
Keep cell classes focused on view rendering, not navigation or business logic. For complex cells, break subviews into helper methods and keep configure idempotent so repeated calls produce consistent state. If loading remote images, cancel stale requests in prepareForReuse to prevent flicker.
For performance, avoid expensive layer operations in cellForItemAt. Precompute view models and apply lightweight updates during scrolling. Use Instruments when scroll performance degrades, because reuse bugs and layout invalidation patterns are easier to diagnose with profiling than by inspection.
Flow layout is fine for simple grids, but modern screens often need mixed sections. UICollectionViewCompositionalLayout gives better control over adaptive cards and section behavior. You can still keep the same custom cell class and reuse strategy.
1let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
2 heightDimension: .absolute(120))
3let item = NSCollectionLayoutItem(layoutSize: itemSize)
4let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
5 heightDimension: .absolute(120))
6let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, item])
Adopting compositional layout early reduces refactor cost when product requirements expand beyond uniform grids.
Common Pitfalls
Forgetting to register the custom cell class before dequeuing.
Reusing cells without resetting state in prepareForReuse.
Hardcoding sizes that break on rotation or split-screen layouts.
Doing heavy work in cellForItemAt, causing dropped frames.
Mixing networking and view logic directly inside cell subclasses.
Summary
Create a dedicated cell subclass with clear configuration APIs.
Register and dequeue cells consistently using a shared reuse identifier.
Choose explicit or self-sizing strategy and test on multiple screen sizes.
Keep cell logic presentation-focused and reset reusable state correctly.
Profile scrolling performance when grids become complex.