UICollectionView Custom Layout: the Ultimate Tutorial – Part 2

Welcome back to the “UICollectionView Custom Layouts: the Ultimate Tutorial” series. This is Part 2, where we will actually tackle the custom layout part.
If you haven’t seen or done the first part, I strongly recommend you to take a look at it, as we will be using the UICollectionView we created there.

Remember what we had at the end of Part 1, when we run the project without the custom layout, and what we would like to achieve?

CollectionView without UICollectionViewFlowLayout       Wireframe of the CollectionView with UICollectionViewFlowLayout

A few words

A few words before we start:

I recommend you to have a look at Session 232 of WWDC 2014: Advanced User Interfaces with Collection Views for the Custom Layout questions, and at Session 226 of WWDC 2014: What’s new in Table and Collection Views concerning the cell sizing. I won’t go into the details here as everything is well explained on the videos.
Here, we will focus on the implementation.

Preparing the cells

First, we need to add a bit of code in the TextCell class, as this cell will need to get resized. Simply paste the following code in TextCell.swift

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let attr = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    attr.frame.size.height = desiredHeight
    self.frame = attr.frame  // Do NOT forget to set the frame or the layout won't get it !!!

    return attr

Let’s add a simpler version of it in the ItemCell class:

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let attr = layoutAttributes.copy() as! UICollectionViewLayoutAttributes
    self.frame = attr.frame

    return attr

This part of the code enables the Layout to calculate the new size of the cell, matching its content.

You don’t need similar code for ImageCell because its size is fixed, set in the custom layout and not re-calculated.

And now, take a deep breath, we will create the long-awaited custom layout!


Let’s start with the first baby step:

Create a new file, subclass of UICollectionViewFlowLayout and name it MyCollectionViewFlowLayout.

Then, let’s add some configuration: section variables, initializations and basic setup.

let cellHeight: CGFloat = 200
var itemWidth: CGFloat = 200
let itemRatio: CGFloat = 1.33
var layoutInfo = [NSIndexPath: UICollectionViewLayoutAttributes]()
var layoutHeaderInfo = [NSIndexPath: UICollectionViewLayoutAttributes]()
var collectionViewWidth: CGFloat = 0
var maxOriginY: CGFloat = 0
var textHeight: CGFloat = 0.0
var itemsPerRow = 0

let numberOfSections = 4
let sectionImage = 0
let sectionText = 1
let sectionItem = 2

override init() {

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)!

func setup() {
    // setting up some inherited values
    minimumInteritemSpacing = 0
    minimumLineSpacing = 0
    scrollDirection = UICollectionViewScrollDirection.Horizontal
    collectionViewWidth = UIScreen.mainScreen().bounds.width
    if let collectionView = collectionView {
        collectionViewWidth = collectionView.frame.size.width
    itemWidth = getItemWidth()
    headerReferenceSize = CGSizeMake(collectionViewWidth, 50)

func getItemWidth() -> CGFloat {
    // calculate here the size of the items -- see example on GitHub

Also, don’t forget to add the class in the Collection View attributes in the Storyboard. Select “Custom” and type “CustomCollectionViewLayout” in the class field. Obvious, but oh so easy to forget ;)


Now, let’s tackle the heart of the problem custom layout:

The first function to create is prepareLayout, which, as its name states, prepares the layout.

The documentation says :

“Tells the layout object to update the current layout.
Layout updates occur the first time the collection view presents its content and whenever the layout is invalidated explicitly or implicitly because of a change to the view. During each layout update, the collection view calls this method first to give your layout object a chance to prepare for the upcoming layout operation.
The default implementation of this method does nothing. Subclasses can override it and use it to set up data structures or perform any initial computations needed to perform the layout later.”

In this function, we will loop over the sections and items to get the actual size of each element, and to keep track of the height of the content.
And as we would like to have a code as elegant as possible, we will do the size calculation in a separate function, called frameForItemAtIndexPath.

override func prepareLayout() {
    maxOriginY = 0
    setup() // can also be triggered only if the layout or collectionView bounds have changed
    for i in 0 ..< numberOfSections {
        if let collectionView = self.collectionView {
            for j in 0 ..< collectionView.numberOfItemsInSection(i) {
                let indexPath = NSIndexPath(forRow: j, inSection: i)
                let itemAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
                itemAttributes.frame = frameForItemAtIndexPath(indexPath)
                if (indexPath.section < sectionItem) {
                    maxOriginY += itemAttributes.frame.size.height
                } else if (indexPath.section == sectionItem) {
                    let allRows = (indexPath.item % Int(itemsPerRow)) == (Int(itemsPerRow) - 1)
                    let lastRow = indexPath.item == (collectionView.numberOfItemsInSection(i) - 1)
                    if (allRows || lastRow) {
                        // add height only if there is "itemsPerRow" items per row, or if multiple rows: last item per row
                        maxOriginY += itemAttributes.frame.size.height
                layoutInfo[indexPath] = itemAttributes

func frameForItemAtIndexPath(indexPath: NSIndexPath) -> CGRect {
    if (indexPath.section == sectionImage) {
        return CGRectMake(0, maxOriginY, collectionViewWidth, collectionViewWidth)
    } else if (indexPath.section == sectionText) {
        var currentCellHeight = max(textHeight, cellHeight)
        if let collectionView = self.collectionView {
            if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? TextCell {
                textHeight = max(textHeight, cell.frame.height)
                currentCellHeight = textHeight
        return CGRectMake(0, maxOriginY, collectionViewWidth, currentCellHeight) // width, height
    } else if (indexPath.section == sectionItem) {
        let currentColumn = ceil(CGFloat(indexPath.item % itemsPerRow))
        let posX = currentColumn * itemWidth
        let rect = CGRectMake(posX, maxOriginY, itemWidth, itemWidth * itemRatio)
        return rect
    return CGRectZero

Then, we will make sure that the UICollectionView has the right size:

override func collectionViewContentSize() -> CGSize {
    guard let collectionView = self.collectionView else { return CGSizeMake(UIScreen.mainScreen().bounds.width, maxOriginY)}
    return CGSizeMake(collectionView.frame.width, maxOriginY)

Pretty straight forward.

Layout Attributes

Two more thing and we’re done! Now that we have calculated the size of the cells and that we know the size of the UICollectionView, we can determine the attributes, ie what will appear on screen in a rect or at a certain indexPath:

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    var allAttributes: [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()

    for (_, attributes) in layoutInfo {
        if CGRectIntersectsRect(rect, attributes.frame) {
    return allAttributes

override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
    return layoutInfo[indexPath]

Should invalidate layout

And the last step to finalize the layout: we want to invalidate the layout when needed, ie make sure the cells are calculated according to the actual space available, and not according to the layout estimated size.

override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
    // if you don't want to re-calculate everything on scroll
    return !CGSizeEqualToSize(newBounds.size, self.collectionView!.frame.size)

override func shouldInvalidateLayoutForPreferredLayoutAttributes(preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
    if (originalAttributes.indexPath.section >= sectionText) {
        // true only for sections that needs to be updated (the ones that are changing size)
        // ie. the ones that are using preferredLayoutAttributesFittingAttributes to self-resize
        return true
    return false

I hope this tutorial series helped a bit. As I said at the beginning, I tried to cover different cell configurations to give you the tools to start experimenting with custom layouts, now it’s up to you to go further! And, who knows, maybe Apple will soon release something to help us deal (easily) with variable height in UICollectionViewCells like they did for UITableView

All the code for this tutorial is available on GitHub: SCCollectionViewCustomLayout. As usual, don’t hesitate to fork it and play with it! Also, there is a little bonus: I’ve included a dynamic HeaderView in the UICollectionView and Custom Layout :) You’re welcome!

If you have read that far, thank you! Really! I know it has been long, but you won’t go unrewarded, I have some extra tips to share with you :)

Pro tips:

  • Try it on the iPad, it’s responsive! (if you add viewWillLayoutSubviews() to the UIViewController, like in the example on GitHub)
  • If you are caught in an infinite loop in the UICollectionViewFlowLayout that makes the app crash, double check the size (and potential margins) of the cells you are trying to render. If it doesn’t match with the available space, it will try to calculate it again until it fits, which isn’t likely to happen… and it will eventually crash.

UICollectionView Custom Layout: the Ultimate Tutorial – Part 1

I’m working every day with UICollectionViews and until recently never used a custom layout. I decided to take the plunge and learn how to create one when I had to deal with a complex layout on a UICollectionView and make my designer happy. So here is everything I learned (or everything you need to know — to start with) about custom layouts for UICollectionView. Let’s imagine that you have a “complex” design to integrate in a UICollectionView. And yes, you need a collectionView. When I say complex, I mean different types of cells, some of them will have a fixed … Continue reading UICollectionView Custom Layout: the Ultimate Tutorial – Part 1

How to create a Section Background in a UICollectionView in Swift

I recently faced a small challenge on a UICollectionView, the following screenshot illustrates pretty well the situation. In short: I needed to have a transparent background in a UICollectionView to be able to see through some sections, but I didn’t want to be able to see the background in between the cells on some others. Basically, I needed to be able to set different background colors per section. Well, needless to say that functionality doesn’t exist by default on iOS. Luckily for me I found this great article: Changing the section background color in a UICollectionView from Eric Chapman who … Continue reading How to create a Section Background in a UICollectionView in Swift

Swift Tips #2: setBorder Extension

Happy New Year! This is my first post of 2016, so I would like to thank you all for reading my blog and to wish you a very Happy New Year. May this year be as bug free as possible in your apps as well as in your life! As a New Year gift, I’d like to share with you a little something that I’m using all the time: my setBorder Extension. It is the first member of my “Extensions essentials”. When I integrate a design or debug something, I want to see quickly where my elements are, so I … Continue reading Swift Tips #2: setBorder Extension

Simple UICollectionView pagination with API and Realm database

If you’re using Realm database and a REST API, you may have encountered a pagination issue at some point. That’s what happened in the project I’m working on. Something was wrong with the pagination and I kept adding variables and ifs until I couldn’t remember what was doing what. Then I realized I needed a clean slate, and I created this little project. So, this is a mockup to test a minimum viable pagination and preloading of data from a REST API to feed a UITableView or UICollectionView, using Realm.io database. Just to make things clear, I am not introducing pagination into … Continue reading Simple UICollectionView pagination with API and Realm database