As I mentioned in in earlier posts, recently I have been playing with new UICollectionView APIs introduced at WWDC 2019. 2 Weeks ago, I started my ambitious project to re-create an App Store using Collection View and new Composite Layout APIs.

When I was done, I tweeted a video with full demo,

As promised in the Tweet, here's the related blog post on constructing App Store layout on iPad solely using these new APIs which allows us to create any complex layout with minimal efforts.

So let's begin!

The layout has three main parts,

  1. Header (Multiple in various section)
  2. CollectionView Cells (Multiple)
  3. Footer (Just one is Quick Links section)

Headers

There are multiple header views in our App Store layout. However, we can divide them into 3 main categories,

  1. Main Header (Large and Bold Title  + Image on the right)

2.   Sub header (Medium title + Button on the right)

3.   Sub header with image (Medium title + Image + Button on the right)

4. Plain header (Just with Medium title)

Cell

Since we won't be adding fancy cells to our collection views we will use only one type with single UILabel. All we will do is to change the size of this cell based on the applied layout.  We will populate with UILabel with sequence number associated with it.

We will be using only one footer in our layout which will appear right below the Quick Links section. This footer view will contain two button stacked horizontally next to each other.

Please note that for the sake of simplicity and the aim to keep things less verbose and focused solely on composite layout creation, I won't be providing source code for headers, footer or cell creation. We will assume that these views are provided as is and we're just using them for creating the layout
The full source code including the one for creating these fundamental components will be made available on the Github later

Registering components in the UICollectionView

Since we already have all the fundamental components and know how to utilize them, let's start by registering these classes to UICollectionView

Headers

  • Main Header
collectionView.register(MainHeaderView.self, forSupplementaryViewOfKind: Constants.mainHeaderElementKind, withReuseIdentifier: MainHeaderView.reuseIdentifier)
  • Sub header, Sub header with image, and Plan header
collectionView.register(SubHeaderView.self, forSupplementaryViewOfKind: Constants.subHeaderElementKind, withReuseIdentifier: SubHeaderView.reuseIdentifier)
We will use the same cell type for all types of sub and plain headers. The view models thus passed to SubHeaderView will decide whether we're showing Sub header, Plain header, or the Sub header with image

Cell

collectionView.register(NewUpdateCollectionViewCell.self, forCellWithReuseIdentifier: NewUpdateCollectionViewCell.reuseIdentifier)
collectionView.register(QuickLinksFooterView.self, forSupplementaryViewOfKind: Constants.quickLinksFooterElementKind, withReuseIdentifier: QuickLinksFooterView.reuseIdentifier)

Now that we have basic foundation of our layout, let's start constructing App Store layout section-by-section,

In order to maintain simplicity and keep the size of this post to minimum, I won't be adding a code to append headers and footers to sections. The aim of this post is to demonstrate use of Composite layout to create complex layouts without getting involved in intricate and unnecessary details on how to integrate supplementary views.
If you want to know more about supplementary views and how to integrate them with Collection View on iOS 13, you can refer to my previous Advanced Collection View Layout in iOS 13 - Supplementary Views post

Section 1

  • Highlighted Apps
  1. Group will take 95% of total parent container width (To partially display the next group in row)
  2. Group will have fixed height of 300px
  3. Each group will have 2 items arranged horizontally next to each other
  4. Each item will take 100% of available group height and 50% of available group width
  5. We will also assign the value of groupPaging to orthogonalScrollingBehavior of section to make it horizontally scroll one group at a time

Now let's write some code for this logic,

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

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

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

Sections 2, 8, 9, 10, 11

  • Apps We Love Right Now
  • School Essentials
  • Back to School Shopping
  • Guided Meditation
  • Sticker
All the above sections follow the same layout, so we will use the same explanation and code for all of them.
  1. We want to leave 5% of space on the right for next available group so we will create the first group occupying 47.5% of total width followed by another group occupying same proportion of width leaving last 5% space for our next group
  2. Group is highlighted in above diagram which is followed by another group with same layout in horizontal direction
  3. Each group will have absolute height of 400px and relative width occupying 47.5% of total container width
  4. Each group will contain 3 vertically stacked items. Each item will occupy 100% of container width and 33% of container height - Making space for exactly 3 items per group
  5. We will also assign the value of groupPaging to orthogonalScrollingBehavior of section to make it horizontally scroll one group at a time

And finally it's time to write some code,

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

        let group = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.475), heightDimension: .absolute(400)), subitem: item, count: 3)
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        section.orthogonalScrollingBehavior = .groupPaging
        return section
    }
    return layout
}

Section 3

  • Editor's Choice
  1. We want to leave 5% of space on the right for next available group so we will create the first group occupying 47.5% of total width followed by another group occupying same proportion of width leaving last 5% space for our next group
  2. Group is highlighted in above diagram which is followed by another group with same layout in horizontal direction
  3. Each group will have absolute height of 350px and relative width occupying 47.5% of total container width
  4. Each group will contain 2 vertically stacked items. Each item will occupy 100% of container width and 50% of container height - Making space for exactly 2 items per group
  5. We will also assign the value of groupPaging to orthogonalScrollingBehavior of section to make it horizontally scroll one group at a time
private func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        
        let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)))
		item.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

		let group = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.475), heightDimension: .absolute(350)), subitem: item, count: 2)
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        section.orthogonalScrollingBehavior = .groupPaging
        return section
    }
    return layout
}

Sections 4, 5

  • Top Free Apps
  • Top Paid Apps

The layout of this section is similar to that of Sections 2, 8, 9, 10, 11 with following change,

  • The absolute height of group should be changed from 400px to 300px

Section 6

  • Top Categories

The layout of this section is similar to that of Sections 4, 5 with following change,

  • The absolute height of group should be changed from 300px to 175px

Section 7

  • Our Favorite Subscriptions

The layout of this section is similar to the Section 1. We can create this layout with following slight changes in the layout code

  • The absolute height of group should be changed from 300px to 175px

Section 12

  • Quick Links

This is also a less complicated section. The only thing we need to pay attention to is the layout should appear to be horizontally centered. As per current App Store design, the number of items in this section are always going to be exactly 6.

  1. Each group takes 50% of total container width with absolute height of 175px
  2. Each item takes 100% of container width of 33% of its height
  3. Height ratio of 33% for an item makes it possible for a group to have maximum 3 items
  4. Items are vertically stacked
  5. Each item is given horizontal padding of 10px and vertical padding of 5px
private func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        
        let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.33)))
        item.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 10.0, bottom: 5.0, trailing: 10.0)

        let group = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .absolute(175)), subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        section.orthogonalScrollingBehavior = .groupPaging
        return section
    }
    return layout
}

Section 13

  • Terms and Conditions

  1. There is only one group and one item
  2. Group has absolute height of 55px and 95% of its container width
  3. Item is set to occupy 100% of container's width and height
private func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        
        let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
        item.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)

		let group = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.95), heightDimension: .absolute(55)), subitems: [item])

        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0)
        section.orthogonalScrollingBehavior = .groupPaging
        return section
    }
    return layout
}
And that should be all folks. Hope these example were useful for you to get started with Composite Layout on iOS 13. All these examples are available on GitHub to download with source code. You can just clone the repository and switch to the branch named iPad-App-Store-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