The Power of UICollectionView Compositional Layout | Swift | UIKit

Omar Radwan
8 min readJul 6, 2022

--

A Guide To Build Complex Layout

Hello, welcome to my article. We all know that if we have a complex design and repeatable sections the first thing that comes to our minds is UICollectionView.

At WWDC19, Apple introduces a powerful and unbelievable API for building complex layouts. UICollectionViewCompostionalLayout was made to simplify the collection view layouts using a declarative way without the need for nested collection views to achieve customization.

Overview of Basic UICollectionView

We will not talk too much about the UICollectionView layout because most of us know everything about it and if not you can check this link then, you can back and continue learning UICollectionView Compositional Layout. In CollectionView we can control the size of our cells and achieve simple layouts by using UICollectionViewFlowLayout & UICollectionViewLayout.

UICollectionViewFlowLayout

let's see this example If we need to achieve this design of the App Store page, we will face complexity in our code and our brain get mad.

There is a Collection view that represents a Collection view cell inside the biggest Collection view in one section!!. That’s absolutely so hard for us.

Intro to UICollectionView Compositional Layout

Now, in Compositional layout Forget everything you know about collection view layouts! yes, that’s it. But we will think a bit differently about traditional section- and item-based layouts. In our flow layout, a section was comprised of multiple items, laid out in horizontal lines that were stacked vertically.

UICollectionCompositionalLayout introduces a new layer between the section layer and item layer and its Group and I’ll make you understand what I mean. CollectionView layout is comprised of sections, sections are comprised of groups, and groups are comprised of items and optionally other groups. Let’s Take a look at how our layout structure can be modified to incorporate groups.

A compositional layout with items nested in groups vertically within a section.

And there is another picture that shows how Compositional layout work (App Store) Ex.

A compositional layout with items nested in groups horizontally within two sections.

Sections and items described in a compositional layout correspond 1-to-1 with the sections and items of our collection view data source. Groups, however, don’t have a data source equivalent, nor do they render content like items/cells. They’re used solely to describe the layout of our items within a section.

In the diagram above, each group represents a horizontal line of our layout. We’ll get fancier in a bit, but for now, we’ll structure our first compositional layout in this manner. Speaking of our first compositional layout, let’s get started on that now.

Our Example

This is the project we will create and I promise you’ll learn a lot from it. You can find the project here. We will not touch everything in the project like (Cell design and header view…etc) but we will focus on how to make this layout. Now we will speak to our first compositional layout so let’s get started.

UICompositional Layout in coding

Before everything, we will set up our Layout and then set this layout to our CollectionViewLayout.

Init our UICollectionViewCompositionalLayout

we create a layout from the UICollectionViewCompositionalLayout type that gives us the section index and now we can create in each section our custom layout that will return a NSCollectionLayoutSection.

Note that NSCollectionLayoutSection simply describes the layout of any section in the layout we’re creating. We’re not adding any data to the collection view here. Let’s make it cleaner and create a function that returns a NSCollectionLayoutSection and then, calls this function in our Switch-Case above.

func foodBannerSection()-> NSCollectionLayoutSection {     let section = NSCollectionLayoutSection(group: group)     return section
}

Okay, but we need to declare that group. That’s right… because a layout is comprised of sections, which are comprised of groups, which are comprised of groups and items. Let’s work backward from here.

A group is represented by a NSCollectionLayoutGroup. To create one, we need to describe its size relative to its containing section, specify whether we want it to lay items out horizontally or vertically, and describe the items it will contain. In our case now We’ll use the class function NSCollectionLayoutGroup.horizontal(layoutSize:subitems:) to do this since we want our group to lay out its items horizontally, but first, we need to understand the sizing of our group and our items.

Sizing is describing groups and items in relation to their respective container size or to their super container size. More specifically, the size of a group can be described relative to its containing group or section’s size, and the size of an item can be described relative to its containing group’s size. We can describe our size to group or item by NSCollectionLayoutSize

let groupSize = NSCollectionLayoutSize(widthDimension: , heightDimension: )let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

Now, What are the width and height dimensions for our group?. You can express a group’s or item’s dimensions using an absolute, estimated, or fractional value.

Use an absolute value to specify exact dimensions, like a 44 x 44 point square:

let absoluteSize = NSCollectionLayoutSize(widthDimension: .absolute(44),                                         heightDimension: .absolute(44))

Use an estimated value if the size of your content might change at runtime, such as when data is loaded or in response to a change in system font size. You provide an initial estimated size and the system computes the actual value later.

let estimatedSize = NSCollectionLayoutSize(widthDimension: .estimated(200),                                          heightDimension: .estimated(100))

Use a fractional value to define a value that’s relative to a dimension of the item’s container. This option simplifies specifying aspect ratios. For example, the following item has a width and a height that are both equal to 50% of its container’s width, creating a square that grows and shrinks as the size of its container changes.

let fractionalSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),                                           heightDimension: .fractionalWidth(0.5))

Now that’s my dimensions for the first section. We don’t use .estimated here because I’m sure that my group will not be bigger at run time or something like that. Our group takes 70% of our section width and 255 for height, you can also make heightDimension: .fractionalHeight(0.8) that depends on your look.

func foodBannerSection()-> NSCollectionLayoutSection {     let groupSize = NSCollectionLayoutSize(widthDimension:        .fractionalWidth(0.7), heightDimension: .absolute(225))     let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])     let section = NSCollectionLayoutSection(group: group)     return section
}

We now need to define our layout’s items, which we used to create the NSCollectionLayoutGroup above. Items in the layout are represented by NSCollectionLayoutItem, and just like groups, they are initialized with an NSCollectionLayoutSize. Now that we know how sizing works, this should be easy. Each item is filled by the group which it’s width is 70% of our section so, our item size will be taken 100% from his group size

func foodBannerSection()-> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .absolute(225)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) return section
}

Now for the main event. The most amazing feature of compositional layouts.

Orthogonal Scrolling

We refer to this behavior as “orthogonal scrolling”. That is part of our layout scrolls along the opposite axis of the scrolling container. In this example, part of the layout scrolls horizontally in a vertically scrolling container.

How would you build this? I can’t tell you how many times I’ve nested horizontally scrolling flow-layout collection views inside of vertically scrolling table views. Scroll performance and view hierarchy architecture be damned!

What if I told you that you can achieve this behavior, allowing sections of your collection view to scroll horizontally within a vertically scrolling list without incurring a massive performance hit and with One Line Of Code.

        section.orthogonalScrollingBehavior = .continuous
//or
section.orthogonalScrollingBehavior = .groupPaging
//or
section.orthogonalScrollingBehavior = .paging
//or
section.orthogonalScrollingBehavior = .groupPagingCentered
//or
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary

Now let’s add it to our function.

func foodBannerSection()-> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .absolute(225)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
return section
}

And now let’s see what we have done.

That looks great but now I need to add some padding between groups and each other and also some padding to our leading, top, and bottom section. We can do it by NSDirectionalEdgeInsets .

That’s pretty cool right :D

Funny Bonus Animation

Let’s play with some animation and scrollOffest by adding this to the section.

section.visibleItemsInvalidationHandler = { (items, offset, environment) in     items.forEach { item in     let distanceFromCenter = abs((item.frame.midX - offset.x) - environment.container.contentSize.width / 2.0)     let minScale: CGFloat = 0.8     let maxScale: CGFloat = 1.0     let scale = max(maxScale - (distanceFromCenter / environment.container.contentSize.width), minScale)     item.transform = CGAffineTransform(scaleX: scale, y: scale)     }}

Now you understand how every layout work and how to make your custom layout size so now, I will see you how I implement section 1and 2 in coding and I swear you will get everything and deal with it.

Category section (1)
Restaurant list section (2)

Supplementary Items

We’ve accomplished a basic layout of our cells, but if you’ve worked with collection views before, you might know that your data source is also capable of vending reusable supplementary views to create things like headers, footers, and accessory views, and the like. So how do those fit into compositional layouts? Before we get there, I made a custom header and footer FilterHeaderView & DividerFooterView.

Make register to them first and let’s add a method to our data source to create a view we’d like to use as a header for the sections in our collection view.

collectionView.register(FilterHeaderView.self,     forSupplementaryViewOfKind: "Header", withReuseIdentifier: FilterHeaderView.headerIdentifier)collectionView.register(DividerFooterView.self, forSupplementaryViewOfKind: "Footer", withReuseIdentifier: DividerFooterView.footerIdentifier)

And Then, add this to restaurantsListSection()

section.boundarySupplementaryItems = [.init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(30)), elementKind: "Header", alignment: .top),.init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(30)), elementKind: "Footer", alignment: .bottom)]
Header & Footer

Decoration Items

In addition to supplementary items, we can customize our section layout with decoration items. This will allow us to easily add backgrounds to our sections. The background view we’ll create is quite simple (a gray rectangle with a corner radius), so we’ll do it in code.

layout.register(SectionDecorationView.self, forDecorationViewOfKind: "SectionBackground")

In our layout declaration, we simply create a new decoration item for our background using NSCollectionLayoutDecorationItem.background(elementKind:), inset it a bit, and set it on our section.

let decorationItem = NSCollectionLayoutDecorationItem.background(elementKind: "SectionBackground")section.decorationItems = [decorationItem]

Conclusion

We’ve come a long way since UICollectionView’s introduction in 2012. Apple and third-party developers have continued to push the envelope with increasingly complex layouts in their apps that look and work beautifully. UICollectionViewCompositionalLayout is an amazing, feature-packed API that addresses the needs of modern app developers.

--

--