In the current year, Apple showed mind-blowing conference WWDC to us. All iOS dev community are focusing on new cool frameworks and features(SwiftUI, Combine, RealityKit, etc.) trying to figure out how they are working and what to do with them. Many other small but very helpful for current applications features somehow stayed off the screen and today I want to share my own research about one of them — UICollectionViewCompositionalLayout.
I am pretty sure that every newbie iOS developer has started his learning path from the list view, this is a crucial UI element you can find almost in all applications. UICollectionView evolved from UITableView, this fancy UI element helps us to put items in a different order on the screen, all that we have to do is to use UICollectionViewFlowLayout or create a custom one extending UICollectionViewLayout.
But stop! Have you ever tried to do that? If you have you know how hard this is, there is a bunch of different methods, we have to use to create a nice behavior, later on, you would figure out that maybe you should add caching or something else.
All these hard things are stopping developers, they are lazy, they don’t want to do something like this and usually, they are trying to find some library which will help them. That’s why this year Apple gives us UICollectionViewCompositionalLayout and this class continues the era of declarative programming.
UICollectionViewCompositionalLayout is a new fancy Layout, with it we can declaratively setup how it should display items in UICollectionView. After a little amount of code layout will understand what to do and will do all the hard work instead of us, with it you don’t have to implement any of those methods from UICollectionViewLayout!
Let’s look at a simple example the code below will create a list in which all the rows have the same absolute height.
Our next step will be to look at all those classes which are involved in Layout creation.
We should use NSCollectionLayoutSize for setting width and height of the item in Layout. It has two main variables which you should setup during initialization.
NSCollectionLayoutDimension has four variants
One interesting thing you can do with it, you can set in the NSCollectionLayoutSize.widthDimension NSCollectionLayoutDimension.fractionalHeight and vice versa. For example NSCollectionLayoutSize.heightDimension = .fractionalWidth(0.5) means that the height of the item will be equal to half of the width group.
Estimated is the most interesting and powerful one, it is calculating the size for the row “on the fly”. If you have tried to do it previously, you know that it does not work at all with complex cells. I have tested current implementation and it looks pretty good to me, I did not see any problems there at all. Only one thing you have to do is to set item and group heightDimension into type estimated. One restriction we have here is that you must not set contentInsets for element, otherwise Layout will warn you and won’t draw respectively.
In this example you will see Layout for the list where size for the elements is calculated during the rendering process.
NSCollectionLayoutItem is responsible for setting item in collection group, it’s initializing with NSCollectionLayoutSize. In this class you can find two variables which help setup insets and spacing.
contentInsets is working in this way: at first Layout calculates the place and size for the element, then it is looking for element insets in this variable. Like I said before, don’t use contentInsets with estimated size.
edgeSpacing is much more interesting, this is the separate class NSCollectionLayoutEdgeSpacing inside which you can find the same variables as for insets(leading, top, trailing, bottom) but you must use the separate class NSCollectionLayoutSpacing there, not just simple float variable. This spacing is used by Layout before calculating the place for the items, you can use it for filling empty space in your Layout. You can find two types of spacing in the class
flexible is for filling empty space which we have in a group
fixed is doing what it is saying, sets fixed spacing
As an example I will create Layout where with edgeSpacing two columns will be placed in the middle of the screen.
NSCollectionLayoutGroup extending NSCollectionLayoutItem. It adds a crucial feature, you can add there as many items as you want to. Section must have only one group, then during rendering process Layout will draw groups depending on how many items we have in the datasource. For example we have one group, and in it we have one element, datasource is “saying” that we have ten items, than ten groups will be drawn, if we have 2 items in a group then 5 groups will be drawn. Groups can be visualized like stacks, they can be vertical or horizontal, you can add there one kind of item or more. If you want to set some space between items you can use interItemSpacing variable.
If you don’t like vertical and horizontal groups, then you can create your own custom variant.
With NSCollectionLayoutGroupCustomItem class you can set object position in the group, also you can use zIndex if needed. In closure NSCollectionLayoutGroupCustomItemProvider we will receive NSCollectionLayoutEnvironment, it contains container size and also its trait collection.
The most important thing is that you can put a group in a group, like in a stack. That’s because group extends NSCollectionLayoutItem. Using this cool feature I have created complex layout with multiple groups.
After looking at all previous section components, I think you should not have any questions about it. Section is the place where we can have one or more groups, groups can have one or more items. Here we can also set additional spacing for groups and for all content.
In addition we have two interesting variables here.
visibleItemsInvalidationHandler — this closure is called before each rendering cycle, you can use it if you want to know what items are currently visible on the screen, change this items layout or know what offset we currently have.
orthogonalScrollingBehavior — with this variable you can set opposite scroll for particular section. If you have ever tried to do that, you know that the simplest way was to put one more collection in the cell, but from the architecture perspective that was really awful. I think this is what we were waiting for for a long time. We have FIVE different scroll behaviours.
In the next example you can see how continuous type is working, this is amazing!
UICollectionViewCompositionalLayout is our main class, we can initialize it with NSCollectionLayoutSection or with closure, this closure will be called when layout needs information about the section, here you can set up different behaviour for different sections depending on container size and trait collection, portrait and landscape type. Using UICollectionViewCompositionalLayoutConfiguration, you can set scroll direction and space between sections.
NSCollectionLayoutSupplementaryItem, NSCollectionLayoutBoundarySupplementaryItem, NSCollectionLayoutDecorationItem
I keep silence about this additional items intentionally because we are not using them in full power usually, but with this new layout system it will be much easier. I will briefly touch all of them and will show examples with them.
NSCollectionLayoutSupplementaryItem is used for items and groups. The key difference here is how we should add this item to layout. For this we have separate class NSCollectionLayoutAnchor, this item is always rendering above the main element(group, item), that’s why we anchor it to the item sides. It is sounds complicated but on practice it is quite easy. Here is Apple doc example
In addition we can specify absolute or fractional offset. The Fractional one is based on the item size.
Here is my Layout example where I put a checkmark on a group
NSCollectionLayoutBoundarySupplementaryItem — this footers and headers, we all know them, right? This item you can add to NSCollectionLayoutSection and UICollectionViewCompositionalLayoutConfiguration. The size you can set with NSCollectionLayoutSize, and where the item will be — with NSRectAlignment. For header you should set NSRectAlignment to .top, for footer — equals to .bottom.
One interesting variable we have — pinToVisibleBounds, we can do a “sticky header/footer” with it. If you set it in true then current item will be visible whenever its section is visible.
In the example I create three items, at the top, left and bottom, one thing I want to note is that fractional size for this item is based on container not on the section, that’s why left item size you can not set as fractional, you should calculate your section size and set it in absolute value. Maybe in release Apple will change that.
And the last thing is NSCollectionLayoutDecorationItem, with it we can set background for section, it is easy to use and has only one static initializer.
This item extends from NSCollectionLayoutItem, that’s why we can set contentInsets for it, with insets you can adjust bounds for the background. One more specific thing for this item, you must register it in UICollectionViewCompositionalLayout.
In the example, I add shadow for the whole section which is quite cool.
UICollectionViewCompositionalLayout is one of the big steps in iOS development, with this tool it is much easier to create the collection of elements, declarative behavior will satisfy 99% developers.
While the SwiftUI is still in active development and there we still don’t have collections at all, I highly recommend using this new Layout in new projects. In the next article, I will try to experiment with UICollectionViewDiffableDataSource.