UICollectionView doesn't support static content layouts as its sibling UITableView. There is a way to simulate it though, and this article will walk through how to do just that.
Tutorial objectives
When using a UITableView
, we have a choice of either creating a table with static cells or to create cell templates that are used to create cells dynamically. The former approach can be used to more quickly implement simple use cases like a screen to allow users to change settings. The latter dynamic approach is used when the number of cells are not known until runtime--such as a list of products in a product catalog stored on a web service.
In this tutorial, we’ll create a UICollectionView
that will serve as a "main menu" for an application. We'll provide the following functionality to the application:
- The collection view will display a fixed number of cells designed within an Xcode storyboard.
- The cells will adapt to the size of the display — for example using a two-column layout on an iPhone X in portrait mode, and a single column on an iPhone SE in portrait mode. In landscape mode, the click-able cells will fill the horizontal space before starting a new row.
- When the user taps on a cell, the application will intercept that event and respond appropriately (in a production app, this may be to fire the correct segue associated with each cell.
To keep the tutorial simple, this application will just display four cells of identical size and layout. But a real application could be much more sophisticated. Each cell could be a different Collection View Cell design, and present entirely different content from the others. But the basic architecture for this approach would be the same.
Step 1: Creating a static layout in Interface Builder
As with creating static UITableView
layouts, the first step is to create a layout in Interface Builder.
Complete the following steps first:
- Open Xcode and create a new single view application
- Add a
UICollectionView
to the View Controller scene in Main.storyboard - Set
ViewController
as theUICollectionView
delegate
anddatasource
- Using the size inspector, customize the
UICollectionView
cell size to w=170, h=80
Customize the default UICollectionViewCell
with the following changes:
- Add a
UIView
, and use constraints to pin it 4 points from the top, bottom, leading and trailing edges (clear the constrain to marginscheckbox). - Add a
UILabel
to theUIView
in step 1, and center it vertically & horizontally in theUIView
container. - Change the background color of the
UIView
to Purple, and theUILabel
text to "Purple Cell". - Using the Identity Inspector, change the
UICollectionViewCell
Collection Reusable View Identifier to "Purple Cell".
Now copy & paste the Purple cell three times. Change the UIView color, the UILabel text and the UICollectionViewCell Reuse Identity to differentiate each of the four cells from each other. When finished your storyboard design should look something like this:

Step 2: Implement the view controller delegates
If you ran the application now, you’d see a screen with an empty UICollectionView
. Why is that? It's because all we really did was to design some templates of what a set of dynamic cells can look like. Even though the layout looks similar to what can be done using a static UITableView
(except for multiple columns), it's not really a static design. But by adding two data source methods, you can provide the missing information to create the layout design using Interface Builder at runtime.
Change the UIViewController
class definition as follows:
class ViewController: UIViewController {
let cellIds = ["Purple Cell",
"Green Cell",
"Blue Cell",
"Red Cell"
]
let cellSizes = Array(
repeatElement(CGSize(width:170, height:80), count: 4))
override func viewDidLoad() {
super.viewDidLoad()
}
}
The cellIds
property contains a list of the Identity properties we assigned to each UICollectionViewCell
designed in Interface builder. These Ids must exactly match the values we assigned to each cell in Interface Builder.
The cellSizes
property stores the size of each cell. In this simple tutorial, all cells will be the same size--but they don't have to be; each cell could have different content and a different size. By defining the sizes here, we're giving ourselves the ability to control cell sizes at runtime via the UICollectionViewDelegateFlowLayout
(we'll do this in a moment).
Add data source delegate methods
Now add the data source methods to the end of the ViewController.swift
file via a swift extension.
extension ViewController: UICollectionViewDataSource {
func collectionView( _ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return cellIds.count
}
func collectionView( _ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) ->
UICollectionViewCell {
return collectionView.dequeueReusableCell(
withReuseIdentifier: cellIds[indexPath.item],
for: indexPath)
}
}
Each of these delegate methods has only one line of code, and accomplish the following:
numberOfItemsInSection
returns the number of cells in theUICollectionView
, which is inferred by the number of cell Ids we added to thecellIds
property in the last step.- For each of the cells, we create a cell with the corresponding Id. This is the workaround that allows us to make a dynamic
UICollectionView
behave like a staticUITableView
.
Add the layout delegate method
To give us control over the size of each cell at runtime, we can adopt the UICollectionViewDelegateFlowLayout
protocol, and provide an implementation for sizeForItemAt
method. The implementation will simply return the corresponding cellSizes
element corresponding to the indexPath
being laid out.
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return cellSizes[indexPath.item]
}
}
Add the didSelectItemAt delegate method
Since the objective of this tutorial was to create a type of menu, we need to intercept when users tap on items in the menu. To do this, implement a single UICollectionViewDelegate
method. Add the following extension to the bottom of ViewController.swift
.
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
print("User tapped on \(cellIds[indexPath.row])")
}
}
This delegate method is called when the user taps on any of the (four) cells in the UICollectionView
. In response, the sample code just prints the Cell Id of the cell the user tapped on. In a production application, we might instead cast the UITableViewCell
to a custom class, and read metadata from it to decide how to branch the application flow.
Now if you run the application, and press on each button, you should see the following output in the Xcode debug console.
User tapped on Purple Cell User tapped on Green Cell User tapped on Red Cell User tapped on Blue Cell
The completed, flexible layout
If you now run the application on different devices, you can see that we’ve created an almost static UICollectionView
that represents a menu. The advantage of this approach over using a UITableView
is that we have a much more flexible layout that can be presented on different devices.
iPhone X & iPhone SE — Portrait
The Width of the iPhone X and iPhone SE are quite different, so our layout automatically adapts between two columns (X) and 1 column (SE).

iPhone X — Landscape
The iPhone X in landscape has plenty of horizontal space, so all four of the menu items fit on one row.

iPhone SE — Landscape
The iPhone SE in landscape is more constrained horizontally, so the layout automatically flows onto two rows:

Changing up the cell sizes
One of the advantages to UICollectionView
is how flexible it is when cell sizes are different. By changing the array of cell sizes at the top of UIViewController.swift
to the following, we can observe this flexibility in action.
Change the cellSizes
property in ViewController.swift
to the following:
let cellSizes = [
CGSize(width:210, height:60),
CGSize(width:180, height:100),
CGSize(width:170, height:80),
CGSize(width:150, height:150)
]

Full source code
I hope this tutorial was helpful to you and gave you some ideas for your own applications. You can download the full source code for this tutorial here: