Introduction
Creating a UICollectionView programmatically gives you full control over layout, registration, and constraints without relying on Storyboards. The pattern is straightforward once you break it into parts: create a layout, create the collection view, register a cell, and implement the data source and delegate methods. Programmatic setup is especially useful in reusable components, modular UI code, and dynamic layouts.
Start with a Layout
Every collection view needs a layout object. A UICollectionViewFlowLayout is the simplest starting point.
1import UIKit
2
3let layout = UICollectionViewFlowLayout()
4layout.scrollDirection = .vertical
5layout.minimumLineSpacing = 12
6layout.minimumInteritemSpacing = 12
7layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
This layout decides spacing, direction, and basic item placement.
Create the Collection View
In a view controller, instantiate the collection view with the layout and constrain it to the parent view.
1import UIKit
2
3final class ColorsViewController: UIViewController {
4 private let items = ["Red", "Blue", "Green", "Orange"]
5 private var collectionView: UICollectionView!
6
7 override func viewDidLoad() {
8 super.viewDidLoad()
9 view.backgroundColor = .systemBackground
10
11 let layout = UICollectionViewFlowLayout()
12 layout.scrollDirection = .vertical
13 layout.minimumLineSpacing = 12
14 layout.minimumInteritemSpacing = 12
15 layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
16
17 collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
18 collectionView.translatesAutoresizingMaskIntoConstraints = false
19 collectionView.backgroundColor = .systemBackground
20 collectionView.dataSource = self
21 collectionView.delegate = self
22 collectionView.register(ColorCell.self, forCellWithReuseIdentifier: ColorCell.reuseID)
23
24 view.addSubview(collectionView)
25 NSLayoutConstraint.activate([
26 collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
27 collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
28 collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
29 collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
30 ])
31 }
32}
That is the core setup. The collection view exists, is attached to the view hierarchy, and knows which cell class to reuse.
Build a Custom Cell
A custom cell keeps presentation logic out of the controller.
1import UIKit
2
3final class ColorCell: UICollectionViewCell {
4 static let reuseID = "ColorCell"
5
6 private let titleLabel = UILabel()
7
8 override init(frame: CGRect) {
9 super.init(frame: frame)
10 contentView.backgroundColor = .systemBlue
11 contentView.layer.cornerRadius = 12
12
13 titleLabel.translatesAutoresizingMaskIntoConstraints = false
14 titleLabel.textColor = .white
15 titleLabel.font = .preferredFont(forTextStyle: .headline)
16
17 contentView.addSubview(titleLabel)
18 NSLayoutConstraint.activate([
19 titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
20 titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
21 ])
22 }
23
24 required init?(coder: NSCoder) {
25 fatalError("init(coder:) has not been implemented")
26 }
27
28 func configure(with text: String) {
29 titleLabel.text = text
30 }
31}
Programmatic cells are a good place to add subviews, Auto Layout constraints, and styling.
Implement the Data Source
Now provide the item count and cell configuration.
1extension ColorsViewController: UICollectionViewDataSource {
2 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
3 items.count
4 }
5
6 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
7 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ColorCell.reuseID, for: indexPath) as! ColorCell
8 cell.configure(with: items[indexPath.item])
9 return cell
10 }
11}
Without these methods, the collection view appears but shows no content.
Size the Items
If you use UICollectionViewFlowLayout, implement UICollectionViewDelegateFlowLayout for item sizing.
1extension ColorsViewController: UICollectionViewDelegateFlowLayout {
2 func collectionView(
3 _ collectionView: UICollectionView,
4 layout collectionViewLayout: UICollectionViewLayout,
5 sizeForItemAt indexPath: IndexPath
6 ) -> CGSize {
7 let width = (collectionView.bounds.width - 44) / 2
8 return CGSize(width: width, height: 120)
9 }
10}
This creates a simple two-column grid with spacing already accounted for.
When Programmatic Setup Helps Most
Code-based setup is especially useful when:
the UI is reused across modules
layout changes depend on runtime configuration
the project avoids Storyboards by convention
the collection view lives inside a custom reusable view
It also makes review diffs cleaner because the behavior is visible in Swift code instead of Interface Builder metadata.
Common Pitfalls
Forgetting to register the cell class or nib before dequeueing cells.
Adding the collection view without disabling autoresizing masks or adding constraints.
Implementing the collection view but forgetting to set the dataSource and delegate.
Hardcoding item sizes without accounting for section insets and spacing.
Putting too much presentation logic into the view controller instead of a custom cell.
Summary
A programmatic UICollectionView starts with a layout, a registered cell, and data source wiring.
'UICollectionViewFlowLayout is the easiest default layout for grids and lists.'
Custom cells keep styling and subview configuration organized.
Auto Layout constraints are required if the collection view is created in code.
Programmatic setup is ideal when you want precise control and reusable UI code.