Swift
iOS Development
UICollectionView
Programming Tutorial
App Development

How to create UICollectionViewCell programmatically

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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.

swift
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.

swift
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:

swift
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.

Configure modern list or compositional layouts when needed

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.

swift
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.

Course illustration
Course illustration

All Rights Reserved.