This blog post is based on the Session 215 - Advances in Collection View Layout from WWDC 2019. I would highly recommend to go through this video to learn basics about new collection view APIs and basics of Compositional layout on iOS.

In Session 215 of WWDC 2019, Apple introduced a brand new API for collection views to facilitate building complex layouts with minimal lines of code with ease of extension and maintenance. The thing I was most excited about was introduction of compositional layout.

At its core, Composite Layout is made up of four components going from bottom to top,

  1. Item
  2. Group
  3. Section
  4. Layout

Depending on how you leverage this structure, you can make layouts ranging from simple to most complex which would otherwise have been nearly difficult had it not been for third party APIs. In order to keep things simple, reduce the length of this post, and keep the topic focused, I am solely going to concentrate on how to make a custom layout using new APIs.

Here, I will assume that you already have set up

  1. Collection view (Programmatically or using Storyboards)
  2. Respective dataSource and delegates
  3. cellForRowAtIndexPath datasource method or using diffable data source (More info on diffable here)

Here, we will touch only on the subject of creating and assigning a UICollectionViewLayout to the collection view,

collectionView.collectionViewLayout = createLayout()

All we're going to see is a different variations of createLayout() method which will give us range of layout options,

  1. Simple layout

To begin with, we want to create a layout like this, this is similar to the one Apple demonstrated during the presentation,

This might have looked intimidating without composite layout, but turns out it's just few lines of code when new collection view APIs are used. Here we will make a division like this

  1. A leading item
  2. A trailing group containing two nested items
  3. A nested group containing both leading item and trailing group from previous steps

We will start by creating a layout for leading item. Since we want to maintain unequal widths for both parts, we will assign fractional width of 0.7 to leading item. (Which means whatever the width of parent container, our leading item will end up taking 70% container width)

let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1.0)))

leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

Please note that we have also set contentInsets with 5 pixels on each side and this is optional and depends what your design team wants

Now, we will start constructing trailing group with two items stacked vertically on each other. We want this trailing group to take 30% of total width of its parent container. (Because we already allocated 70% to leading group in the previous step) The group will have

  1. Two items vertically stacked
  2. Each item taking 100% of its parent container width
  3. Each item taking 50% of its parent container height

let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

// The following line could also be
//let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0)), subitems: [trailingItem])

let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0)), subitem: trailingItem, count: 2)

Now all we have to do it to combine leadingItem and trailingGroup into another nested group. We will set the nested group dimensions relative to the full screen size,

  1. It should occupy 85% of total screen width
  2. It should occupy 40% of total screen height
let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.85), heightDimension: .fractionalHeight(0.4)), subitems: [leadingItem, trailingGroup])

We're almost done. All we have to do now is to create a section embedding this nested group inside and assigning contentInsets if applicable,

let section = NSCollectionLayoutSection(group: nestedGroup)
section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

If we combine all the previous steps, our createLayout() function will look like this,

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1.0)))
            leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

            let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)))
            trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

            let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0)), subitem: trailingItem, count: 2)

            let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.85), heightDimension: .fractionalHeight(0.4)), subitems: [leadingItem, trailingGroup])

            let section = NSCollectionLayoutSection(group: nestedGroup)
            section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

            return section
    }

    return layout
}

// And somewhere in your file, assign this layout to the collection view (Assuming you've already set up your data sources)
collectionView.collectionViewLayout = createLayout()

Here is little graphical summary of how this code gets converted into the layout,

Now if you run your code, this layout will scroll vertically - Which is expected.

However, if you want it to scroll horizontally this is just one line change

// Possible values of orthogonalScrollingBehavior are
// .none, .continuous, .continuousGroupLeadingBoundary, .paging, .groupPaging, and .groupPagingCentered

section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
Note: For all the subsequent examples, we will be using .continuousGroupLeadingBoundary for the orthogonal scrolling behavior. However, the choice of scrolling behavior or lack of it depends on your use-case and there is no hard and fast rule which one you should be using

2.  Vertical tri-section layout

Now we will move on to bit more complex layout I affectionately call Vertical tri-section layout. To give you little idea about it, this is how it's supposed to look like,

Here's how our division will look like

  1. Vertically divide this layout in three sections
  2. First section takes 33% of total container width and contain 3 items vertically stacked each taking 33% of its parent container width
  3. Section section takes 33% of its parent container width and occupies 100% of its height
  4. Third section, just like the first, takes 33% of total container width and contain 3 items vertically stacked each taking 33% of its parent container width

Let's start writing some code now,

Leading section

let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.3)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let leadingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1.0)), subitem: leadingItem, count: 3)

Middle section

let middleItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1.0)))
middleItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

And finally, trailing section

let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.3)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1.0)), subitem: trailingItem, count: 3)

Nesting all of the together in the top level nestedGroup and converting it to section

let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.85), heightDimension: .fractionalHeight(0.5)), subitems: [leadingGroup, middleItem, trailingGroup])

let section = NSCollectionLayoutSection(group: nestedGroup)
section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary

If we combine all the previous snippets and put them inside createLayout() method, it will look like this

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.3)))
        leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let leadingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1.0)), subitem: leadingItem, count: 3)

        let middleItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1.0)))
        middleItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

        let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.3)))
        trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1.0)), subitem: trailingItem, count: 3)

        let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.85), heightDimension: .fractionalHeight(0.5)), subitems: [leadingGroup, middleItem, trailingGroup])

        let section = NSCollectionLayoutSection(group: nestedGroup)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary

        return section
    }

    return layout
}

// And somewhere in your file, assign this layout to the collection view (Assuming you've already set up your data sources)
collectionView.collectionViewLayout = createLayout()

Here is little graphical summary of how this code gets converted into the layout,

3.  Image carousel layout

As name suggests, we are going to create a simple layout to support image slider. This is much simpler than the earlier one since we only have one item and group to deal with. Subcomponents are as follows,

  1. An item taking 100% of its parent container width and height
  2. A group occupying 100% of its parent container width and absolute height of 150px
  3. A section encapsulating this group with 5px padding on each side

First off, an item taking 100% of its parent container width and height,

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 15.0)

Now, a group

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

And finally, a section encapsulating this group,

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

section.orthogonalScrollingBehavior = .paging

Please note how we set the orthogonalScrollingBehavior to paging. When this parameter is set, page size is equal to the collection view's bounds. It means, every time we swipe, it's going to display the next page.

Combining everything in our createLayout method,

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 15.0)

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

        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

        section.orthogonalScrollingBehavior = .paging

        return section
    }

    return layout
}

// And somewhere in your file, assign this layout to the collection view (Assuming you've already set up your data sources)
collectionView.collectionViewLayout = createLayout()

As a bonus, here's video of image slider in action,

Here's the demonstration of how source code gets mapped to image slider layout,

4.  Tree layout

This layout is bit more complicated and my sincere apologies for misleading name. I couldn't come up with suitable name for this layout, but looks at the layout and decide by yourself which name would you like to assign to it.

Please bear with me. This is going to take little bit longer to explain all the subcomponents of this layout. But trust me, I will try to make it as simple as possible

This component contain,

  1. Nested group

1. Leading full group

1. Leading top item

    2. Leading middle group

    1. Leading middle item (Count: 2)

    3. Leading bottom group

    1. Leading bottom item (Count: 2)

2. Trailing full group

1. Trailing top group

1. Trailing top item (Count: 2)

2. Trailing middle group

1. Trailing middle item (Count: 2)

3. Trailing bottom item

So now we're done with the hierarchical structure. Let's start converting it into the actual Swift code piece-by-piece

Leading group

Leading top item

let leadingTopItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)))
leadingTopItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

Leading middle group

let leadingMiddleItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
leadingMiddleItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let leadingMiddleGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: leadingMiddleItem, count: 2)

Leading bottom group

let leadingBottomItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
leadingBottomItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let leadingBottomGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: leadingBottomItem, count: 2)

Leading full group

let leadingFullGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(1.0)), subitems: [leadingTopItem, leadingMiddleGroup, leadingBottomGroup])

Trailing group

Trailing top group

let trailingTopItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
trailingTopItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let trailingTopGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: trailingTopItem, count: 2)

Trailing middle group

let trailingMiddleItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
trailingMiddleItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let trailingMiddleGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: trailingMiddleItem, count: 2)

Trailing bottom item

let trailingBottomItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)))
trailingBottomItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

Trailing full group

let trailingFullGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(1.0)), subitems: [trailingTopGroup, trailingMiddleGroup, trailingBottomItem])

Nested Group and Full Section

let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalWidth(0.9)), subitems: [leadingFullGroup, trailingFullGroup])

let section = NSCollectionLayoutSection(group: nestedGroup)
section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary

Now just like previous examples, we will combine the full code into createLayout method, which will return the object of type UICollectionViewLayout that will be directly applied to the UICollectionView

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in

        // Leading Top item
        let leadingTopItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)))
        leadingTopItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

        // Leading Middle Group
        let leadingMiddleItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
        leadingMiddleItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let leadingMiddleGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: leadingMiddleItem, count: 2)

        // Leading Bottom Group
        let leadingBottomItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
        leadingBottomItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let leadingBottomGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: leadingBottomItem, count: 2)

        // Trailing Top Item
        let trailingTopItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
        trailingTopItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let trailingTopGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: trailingTopItem, count: 2)

        // Trailing Middle Group
        let trailingMiddleItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)))
        trailingMiddleItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let trailingMiddleGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)), subitem: trailingMiddleItem, count: 2)

        // Trailing Bottom Item
        let trailingBottomItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)))
        trailingBottomItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

        // Leading Full Group
        let leadingFullGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(1.0)), subitems: [leadingTopItem, leadingMiddleGroup, leadingBottomGroup])

        // Trailing Full Group
        let trailingFullGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(1.0)), subitems: [trailingTopGroup, trailingMiddleGroup, trailingBottomItem])

        // Nested Group
        let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalWidth(0.9)), subitems: [leadingFullGroup, trailingFullGroup])

        let section = NSCollectionLayoutSection(group: nestedGroup)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
        return section
    }

    return layout
}
// And somewhere in your file, assign this layout to the collection view (Assuming you've already set up your data sources)
collectionView.collectionViewLayout = createLayout()

And here's the graphical demonstration of how the source code gets mapped to tree layout,

6.  Square family layout

And this is the final and most complicated layout we will see how to construct using composite layout APIs. I got to admit, this was the most difficult layout for me and took the maximum time working out mathematics behind it. It involves bit of mathematics, but please bear with me. We will go through it step-by-step.

Here, our constraints are bit restrictive. We have big square appearing in the top left corner surrounded by more smaller squares. Here's how we will divide this structure into separate sub-components.

  1. Nested group

1. Left nested group

1. Top leading item

2. Middle leading group

1. Middle leading item (Count: 2)

3. Bottom leading group

1. Bottom leading item (Count: 2)

2. Right nested group

1. Right vertical item (Count: 4)

Top leading item

As evident from designs, we want top item to take 66.67% of total width and 50% of total height of its parent container. However, here we will create divisions like this - Left container vs. Right container

So our top leading item will be the part of left container group. This item will take 100% of total parent width (In composite layout terminology, .fractionalWidth(1.0)). Since we want height to match with width, we will set height to .fractionalWidth(1.0) as well.

let topLeadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(1.0)))
topLeadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

Middle leading group

This group is a part of Left container as well - Appearing horizontally in the middle section. We will create individual Middle leading item to take 50% of total parent container width and set the height to the same value.

Middle leading group will take 100% of total parent width and only 50% of total width as height (To maintain the "square" look of its child items)

let middleLeadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(0.5)))
middleLeadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let middleLeadingGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)), subitems: [middleLeadingItem])

Bottom leading group

It will follow exact same structure as Middle leading group except that it will appear at the end in the left container

let bottomLeadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(0.5)))
bottomLeadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
let bottomLeadingGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)), subitems: [bottomLeadingItem])

Left nested group

Left nested group will take 2/3rd of total parent container width and 100% of its height

let leftNestedGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.6667), heightDimension: .fractionalHeight(1.0)), subitems: [topLeadingItem, middleLeadingGroup, bottomLeadingGroup])

Right vertical item (Count: 4)

Right vertical item is a part of right vertical group which ultimately is a part of right container. Since we want these items to be uniformly distributed, we will set the width to full parent container width, but height to only 25% of total parent container height.

let rightVerticalItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)))
rightVerticalItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

Right vertical group

Right vertical group will take 1/3rd of total parent container width and 100% of its height

let rightNestedGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3334), heightDimension: .fractionalHeight(1.0)), subitems: [rightVerticalItem])

Nested Group

Now this is the final step. Here we will combine left and right nested group into top-level nested group to form the final layout. Here we will occupy 90% of total container width. But for height we will need 1/3rd extra since layout aspect ratio is more like 1:1.34

let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalWidth(1.20006)), subitems: [leftNestedGroup, rightNestedGroup])

let section = NSCollectionLayoutSection(group: nestedGroup)
section.contentInsets = NSDirectionalEdgeInsets(top: 100.0, leading: 35.0, bottom: 5.0, trailing: 5.0)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
return section

And if we combine all the previous snippets to create a square family layout, it will look like this,

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in

        let topLeadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(1.0)))
        topLeadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

        let middleLeadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(0.5)))
        middleLeadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let middleLeadingGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)), subitems: [middleLeadingItem])

        let bottomLeadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(0.5)))
        bottomLeadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let bottomLeadingGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)), subitems: [bottomLeadingItem])

        let leftNestedGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.6667), heightDimension: .fractionalHeight(1.0)), subitems: [topLeadingItem, middleLeadingGroup, bottomLeadingGroup])

        let rightVerticalItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.25)))
        rightVerticalItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        let rightNestedGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3334), heightDimension: .fractionalHeight(1.0)), subitems: [rightVerticalItem])

        // 0.9 * 0.6667 + 0.9 * 0.6667 = 1.20006 total height of the container view
        let nestedGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalWidth(1.20006)), subitems: [leftNestedGroup, rightNestedGroup])

        let section = NSCollectionLayoutSection(group: nestedGroup)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
        return section
    }

    return layout
}

And here's the graphical demonstration of how the source code gets mapped to square family layout,

And that should be all folks. Hope these example were useful to you. All these examples are available on GitHub to download with source code. You can just clone the repository and switch to the branch named composite-layout-examples, to see all these examples in action
If you have further questions or need help with any design where composite layout may be useful, feel free to get in touch with me