Custom UICollectionViewLayout Tutorial With Parallax

Introduced in iOS6, UICollectionView is a first-class choice for advanced customization and animation. Learn more in this UICollectionViewLayout tutorial. By .

Leave a rating/review
Save for later
Share

Note: This tutorial uses Xcode 9.0 and Swift 4.

Introduced in iOS 6 and refined with new features in iOS 10, UICollectionView is the first-class choice to customize and animate the presentation of data collections in iOS applications.

A key entity associated with UICollectionView is the UICollectionViewLayout. The UICollectionViewLayout object is responsible for defining the attributes of all the elements of a collection view such as cells, supplementary views and decoration views.

UIKit offers a default implementation of UICollectionViewLayout called UICollectionViewFlowLayout. This class lets you set up a grid layout with some elementary customizations.

This UICollectionViewLayout tutorial will teach you how to subclass and customize the UICollectionViewLayout class. It will also show you how to add custom supplementary views, stretchy, sticky and parallax effects to a collection view.

Note: This UICollectionViewLayout tutorial requires an intermediate knowledge of Swift 4.0, an advanced knowledge of UICollectionView, affine transforms and a clear understanding of how the core layout process works in the UICollectionViewLayout class.

If you’re unfamiliar with any of these topics, you could read the Apple official documentation…

UICollectionViewLayout tutorial

…or, you can check out some of the excellent tutorials on the site!

At the end of this UICollectionViewLayout tutorial you’ll be able to implement a UICollectionView like the following:

UICollectionViewLayout tutorial

Are you ready to win the Jungle Cup? Let’s go!

Getting Started

Download the starter project for this tutorial and open it in Xcode. Build and run the project.

You’ll see some cute owls laid out in a standard UICollectionView with sections headers and footers like the following:

UICollectionViewLayout tutorial

The app presents the Owls Team’s players who are taking part in the Jungle Soccer Cup 2017. Section headers show their roles in the team while footers display their collective strength.

Let’s have a closer look at the starter project:

Inside JungleCupCollectionViewController.swift file you’ll find the implementation of a UICollectionViewController subclass conforming to the UICollectionDataSource protocol. It implements all the required methods plus the optional method for adding supplementary views.

The JungleCupCollectionViewController adopts MenuViewDelegate too. It’s a protocol to let the collection view switch its data source.

In the Reusable Views folder, there are subclasses of UICollectionViewCell for the cells, and UICollectionReusableView for section header and section footer views. They link to their respective views designed in the Main.storyboard file.

Besides that, there are the custom supplementary views the CustomLayout requires. Both the HeaderView and MenuView classes are subclasses of UICollectionReusableView. They’re both linked to their own .xib files.

MockDataManager.swift file holds the data structures for all the teams. For convenience’s sake, the Xcode project embeds all the necessary assets.

Layout Settings

The Custom Layout folder deserves special attention because it contains two important files:

  • CustomLayoutSettings.swift
  • CustomLayoutAttributes.swift

CustomLayoutSettings.swift implements a structure with all the layout settings. The first group of settings deals with collection view’s elements sizes. The second group defines the layout behaviors, and the third sets up the layout spacings.

Layout Attributes

The CustomLayoutAttributes.swift file implements a UICollectionViewLayoutAttributes subclass named CustomLayoutAttributes. This class stores all the information the collection view needs to configure an element before displaying it.

It inherits the default attributes such as frame, transform, transform3D, alpha and zIndex from the superclass.

It also adds some new custom properties:

  
  var parallax: CGAffineTransform = .identity
  var initialOrigin: CGPoint = .zero
  var headerOverlayAlpha = CGFloat(0)

parallax, initialOrigin and headerOverlayAlpha are custom properties you’ll use later in the implementation of stretchy and sticky effects.

If you implement custom layout attributes, you must also override the inherited isEqual method to compare the values of your properties. Starting with iOS 7, the collection view does not apply layout attributes if those attributes have not changed.

Note: Layout attributes objects may be copied by the collection view. Thus, when subclassing UICollectionViewLayoutAttributes, you must conform to NSCopying by implementing an appropriate method for copying your custom attributes to new instances.

Currently the collection view can’t display all the teams yet. For the moment, supporters of Tigers, Parrots and Giraffes have to wait.

No worries. They will be back soon! CustomLayout will solve the problem :]

The Role of UICollectionViewLayout

The main goal of a UICollectionViewLayout object is to provide information about the position and visual state of every element in a UICollectionView. Please keep in mind a UICollectionViewLayout object isn’t responsible for creating the cells or supplementary views. Its job is to provide them with the right attributes.

Creating a custom UICollectionViewLayout is a three-step process:

  1. Subclass the abstract class UICollectionViewLayout and declare all the properties you’ll need to perform the layout calculations.
  2. Perform all needed calculations to provide every collection view’s element with the right attributes. This part will be the most complex because you’re going to implement the CollectionViewLayout core process from scratch.
  3. Make the collection view adopt the new CustomLayout class.

Step 1: Subclassing the UICollectionViewLayout Class

Inside the Custom Layout group you can find a Swift file named CustomLayout.swift which contains a CustomLayout class stub. Within this class you’ll implement the UICollectionViewLayout subclass and all the Core Layout processes.

First, declare all the properties CustomLayout needs to calculate the attributes.

import UIKit

final class CustomLayout: UICollectionViewLayout {
  
  // 1
  enum Element: String {
    case header
    case menu
    case sectionHeader
    case sectionFooter
    case cell
    
    var id: String {
      return self.rawValue
    }
    
    var kind: String {
      return "Kind\(self.rawValue.capitalized)"
    }
  }
  
  // 2
  override public class var layoutAttributesClass: AnyClass {
    return CustomLayoutAttributes.self
  }
  
  // 3
  override public var collectionViewContentSize: CGSize {
    return CGSize(width: collectionViewWidth, height: contentHeight)
  }

  // 4
  var settings = CustomLayoutSettings()
  private var oldBounds = CGRect.zero
  private var contentHeight = CGFloat()
  private var cache = [Element: [IndexPath: CustomLayoutAttributes]]()
  private var visibleLayoutAttributes = [CustomLayoutAttributes]()
  private var zIndex = 0
  
  // 5
  private var collectionViewHeight: CGFloat {
    return collectionView!.frame.height
  }

  private var collectionViewWidth: CGFloat {
    return collectionView!.frame.width
  }

  private var cellHeight: CGFloat {
    guard let itemSize = settings.itemSize else {
      return collectionViewHeight
    }

    return itemSize.height
  }

  private var cellWidth: CGFloat {
    guard let itemSize = settings.itemSize else {
      return collectionViewWidth
    }

    return itemSize.width
  }

  private var headerSize: CGSize {
    guard let headerSize = settings.headerSize else {
      return .zero
    }

    return headerSize
  }

  private var menuSize: CGSize {
    guard let menuSize = settings.menuSize else {
      return .zero
    }

    return menuSize
  }

  private var sectionsHeaderSize: CGSize {
    guard let sectionsHeaderSize = settings.sectionsHeaderSize else {
      return .zero
    }

    return sectionsHeaderSize
  }

  private var sectionsFooterSize: CGSize {
    guard let sectionsFooterSize = settings.sectionsFooterSize else {
      return .zero
    }

    return sectionsFooterSize
  }

  private var contentOffset: CGPoint {
    return collectionView!.contentOffset
  }
}

That’s a fair chunk of code, but it’s fairly straightforward once you break it down:

  1. An enum is a good choice for defining all the elements of the CustomLayout. This prevents you from using strings. Remember the golden rule? No strings = no typos.
  2. The layoutAttributesClass computed property provides the class to use for the attributes instances. You must return classes of type CustomLayoutAttributes: the custom class found in the starter project.
  3. A subclass of UICollectionViewLayout must override the collectionViewContentSize computed property.
  4. The CustomLayout needs all these properties in order to prepare the attributes. They’re all fileprivate except the settings, since settings could be set up by an external object.
  5. Computed properties used as syntactic sugar to avoid verbose repetitions later.

Now that you’re done with declarations, you can focus on the Core Layout process implementation.

Contributors

Darren Ferguson

Tech Editor

Chris Belanger

Editor

Andy Obusek

Final Pass Editor and Team Lead

Over 300 content creators. Join our team.