Introduction

Collection views are extremely versatile - there’s even speculation that the venerable UITableView will be soft-deprecated in favour of its more flexible and powerful cousin. But this versatility comes with a price - collection views, and particularly collection view layouts, can become complicated, and the official documentation for some of the more interesting areas is a little… sparse.

Apple frequently tells you to use directly or subclass the flow layout. The flow layout does a lot of heavy lifting for you when you’re dealing with a flexible number of arbitrarily sized items that need to make room for each other. This covers a lot of use cases, and delivers you the following advanced features for free, or almost free:

However, getting things for free usually has a price - in this case, the price is a lack of decent understanding of what a collection view layout does or how it works. The flow layout achieves all it does using the same API that is available to you as a third party developer, but it’s not immediately clear how you would create something like the flow layout yourself.

You’re going to learn about the following interesting but poorly documented areas of collection view layouts:

Custom Layouts

If your layout is more complicated than “fit these cells in a line, breaking when you get to the edge of the screen”, or you’re not happy with the decisions that flow layout makes about your cells (for example, the forced “justification” of cells) then you may want to consider a custom layout.

If your layout uses regular, predictable, but non-uniform sizes then a custom layout is also a good choice. Don’t use the flow layout just to get some of its features, like floating section headers. These features are not difficult to implement yourself.

Building a custom layout can be complex, and it’s important to be able to quickly iterate when working. Xcode playgrounds are an excellent place to develop a custom layout. Using the live view feature you can add a collection view to the assistant area and watch it develop as you work on your layout. It’s easy to stand up a simple data source object which returns plain or very simple cells, which you could perhaps tint with colours to let you know where things are going.

Accompanying this article is an Xcode playground demonstrating the code and principles discussed. The layouts included in the playground are designed with code readability and learning in mind, to use them in production may require performance optimisations or other changes. The Previewing page of the playground demonstrates how to make an interactive, scrollable collection view right there in your playground.

One drawback of the playgrounds approach is that things can get worryingly slow - for some layouts there can be a lot of back and forth as attributes are invalidated and recalculated, and you may find yourself thinking that you’re developing something unusable. However, most of this is due to the nature of playgrounds, particularly the live output and logging of each line of code in the page. If you encounter this issue then you can move your layout code to the Sources folder of your playground, where it will be compiled, and will run much faster.

Building a layout subclass

UICollectionViewLayout is an abstract class, and any subclass has to implement a few documented methods for it to be usable:

There is a very simple layout in the Custom Layout page of the playground. You can watch when and how often each method is called by scrolling. This diagram summarises the process:

Flow diagram showing the collection view layout lifecycle

Decoration Views

Your layout can return attributes for three different kinds of views:

To add decoration views to a layout, do the following:

The playground page Decoration View shows a very simple decoration view - all it adds is a single blue square in the top right of the collection view. The blue square moves along with the scrolling.

Adding supplementary views is almost identical, except that your datasource will also get a chance to configure each supplementary view. The decision between using a decoration view or a supplementary view is therefore pretty simple - if you require an opportunity to directly configure each view as it is added, then you’re better off using supplementary views.

Floating views

That decoration view is pretty boring. How would you make it “float” on the layout, so it was always in the top right corner regardless of the scroll position? You can’t just amend the frame in layoutAttributesForElements(in:) - not only is that method not called often enough, it will also cause a runtime exception, as you’re modifying layout attributes without invalidating them. You could invalidate the layout on every bounds change and recalculate every frame, but that’s pretty inefficient. The secret is to use an invalidation context.

There is a lot of information in a collection view layout, and it’s not efficient to have to recalculate the entire layout just because a couple of aspects might need updating. To help with this, you can supply an invalidation context which you can use to specify which parts of the layout need recalculating. Think of your layout as a Lego model - if you wanted to alter one part of it, you don’t need to tear the whole thing down and rebuild, you can just tweak one part!

To make the decoration view in the last example float, you have to update its position when the collection view is scrolled. The cell positions are still valid, since they are moving along with the scroll. Using an invalidation context you can say that only the floating view will need its attributes updating when the bounds of the collection view changes (scrolling constitutes a bounds change).

The Floating Decoration View page of the playground shows how to implement a floating view. In summary:

You can invalidate the layouts of multiple views within a single invalidation context. That’s what happens in the final part of the article.

Self-sizing cells

Invalidation contexts are also used when implementing auto sizing cells in a collection view, and when interactively dragging and dropping cells to re-order them. In fact, much of the “magic” that the flow layout performs for you is done using invalidation contexts, so it’s important to understand how they work.

Self-sizing views are implemented using a rather elaborate dance:

This diagram summarises the process:

The cycle of attribute generation, fitting, preferred attribute generation and invalidation

The Autosizing page of the playground shows an implementation of this. Notice that in this case the layout is in the Sources folder, because autosizing involves many iterations back and forth, and having that code in the playground becomes very slow.

Summary

Collection views are a fundamental component of iOS applications, and are likely to become more so in the future. The ability to build interesting and performant layouts will give your apps an edge, but to do that you need to learn how the fundamental mechanisms of a layout fit together.

Richard Turton

Cocoa Engineer

MartianCraft is a US-based mobile software development agency. For nearly two decades, we have been building world-class and award-winning mobile apps for all types of businesses. We would love to create a custom software solution that meets your specific needs. Let's get in touch.