UICollectionView is one of the most powerful tools in the iOS world. It’s used in a multitude of apps, sometimes so highly customized that it’s hard to recognize original items and views. Whether you’re building a simple table or a complicated calendar app, a collection view is a way to go!

In this article, I’d like to teach you about creating the collection view layouts. Together we’ll implement a horizontal layout with a custom scrolling — like this:

We can break the development of this layout into three separate tasks:

allowing to focus on each item in a collection,

making items snap to collection view’s center when scrolling ends,

increasing spacing between the focused item and its neighbors.

I’m going to walk you through these but first, let’s go briefly over the collection view’s core layout process

Core layout process

The article assumes some knowledge of building custom layouts. If you’re unfamiliar with it, I highly recommend looking at Apple’s Collection View Programming Guide. It’s a great resource about a collection view in general, including creating the custom layouts.

In a nutshell, the collection view works with your custom layout object to manage the overall layout process by calling specific methods and properties. These allow you to calculate the position of items, and to provide the collection view with the information it needs. They are always called in the following order:

prepare() : perform the up-front calculations needed to provide layout information there

: perform the up-front calculations needed to provide layout information there collectionViewContentSize : returns the overall size of the entire content area based on your initial calculation

: returns the overall size of the entire content area based on your initial calculation layoutAttributesForElements(in rect: CGRect) : returns the attributes for cells and views that are in the specified rectangle.

After the layout process is finished, the attributes of your cells and views remain the same until you (or the collection view) invalidate the layout.

Implementing basic layout functions

Prepare

We’ll start by implementing a prepare() method. It’s the place to perform the initial calculations needed to provide the layout attributes for all the items. For performance purposes, it’s a good idea to cache these in a dictionary. Our collection view has only one section, so all we have to do here is to iterate through all the items in that section, and save their attributes:

Attributes are created in createAttributesForItem(at indexPath: IndexPath) function:

Its main purpose is the creation of attributes, but apart from that, it also sets their frame. It’s important that this function doesn’t make any changes to attributes that depend on a collection view’s content offset. This way, we can keep our attributes cached while scrolling and only recreate them when the collection view’s bounds change or data source reloads. To purge attributes, just call removeAll() on the dictionary:

Next, add guard cachedItemsAttributes.isEmpty else { return } to your prepare() function, just above the for in loop. That way attributes will recreate if – and only if – you purged the cache.

Content size

Let’s move to the next must-override: collectionViewContentSize computed property. It returns the overall size of the entire content area based on the initial calculations performed in prepare() . That one’s easy! All we have to do is to find the leftmost and rightmost attributes in our cache:

Layout attributes

All that’s left for now are layoutAttributesForItem(at indexPath: IndexPath) and layoutAttributesForElements(in rect: CGRect) functions. Both rely on previously cached attributes. The first one simply returns attributes for a specified index path:

while the second one checks out which attributes intersect with currently displayed rect and returns them:

Perfect! We overrode all necessary functions and our basic layout is functional.