How to Write Custom UICollectionViewLayout With Real Self-sizing Support
You find various articles on how to write custom layouts for UICollectionView
very often. Many of them do not support real self-sizing, they request item size for all elements in prepare()
and that’s it. This approach is not very pleasant for layout users and also it is not very performant as you basically re-layout all your cells when invalidateLayout()
is called (this means it is called on every layout invalidation). Today we will try to create a simple implementation of UICollectionViewFlowLayout
, that will use single column (thus looking like a UITableView
) and that will support real self-sizing without having any delegates and without the user having to deal with providing correct cell size. We’ll let autolayout take care of it.
To make this post swift, we will provide you with the final layout and we will go through its crucial points. At first we would love to point out that this layout is not an optimal solution but it is the easiest solution, that will get the job done 🙂 For that purpose, we expect at least a basic knowledge of creating UICollectionView layouts.
Layout attributes abstraction
Create your own structure to store layout attributes instead of storing UICollectionViewLayoutAttributes
. It might seem like a good idea to store them, but keep in mind that it contains indexPath
. So if the first item in the collectionView
is deleted, you would need to recreate all the cached attributes and that’s just not worth it. So you cache something that is not indexPath
related, for that we have our LayoutItem
struct.
Caching
At first it is good to cache already computed layout attributes so that when collectionView asks for them, they can be returned immediately. Thus we have cachedAttributes
dictionary. It might be better to use a different structure so we can get all the attributes in a given rectangle quickly, but a dictionary works for us now.
ZIndex
Make sure you define zIndex
for all your layout attributes. This is not documented anywhere, but if you use more complex layouts in your collectionView
cells, you can end up with incorrect initial layout. Adding a non-zero zIndex
fixes that issue. As our TableLayout
counts with multiple sections, we store zIndex
with our layout attributes.
Self-sizing support
To support self-sizing you need a few things. Some things can be copied from UICollectionViewLayout
. First you need some estimated size, in our case it is just height as we only support single-column layout. This estimate is used for initial layout because layout always has to provide some attributes for cells that should be displayed.
When a cell is displayed, it is asked if it is okay with layout attributes provided by the layout using preferredLayoutAttributesFitting(_:). As our cell layout is quite simple, this solution is not used. In this method you would probably want to call systemLayoutSizeFitting(_:). This will tell autolayout to compute the size of the cell. In our case its height. Computed dimensions will be returned and UICollectionView
will pass them to its layout. This will get to layout’s shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:). In our case if preferred height differs from original height, we need to invalidate layout (return true). Basically by implementing this method we are adding self-sizing support to layout.
If layout reports that it needs to be invalidated, it will be asked for invalidation context. Invalidation context is something that wraps changes of layout items and describes how the collectionView should handle those changes. Here we have several things to do – we need to check how the height of the item changed and propagate it to several places. We need to update the content size of the collectionView and update height in cached attributes. Optionally, if the item’s top edge is above collectionView’s top edge, we need to update content scroll offset so we eliminate weird “jumping” caused by height change.
Ready to go
Now you should be ready to create your own layouts that will support real self-sizing and will be pleasant for their users. It would be nicer if creating custom layouts would be better documented by Apple. It is not, so we need to learn a lot of things the hard way. Although it can be painful, we sure are more satisfied when we manage to really create what we wanted, right? 😎