Do we even need Compositional Layouts to design our next iOS app?

stutid

Stuti Dobhal

Posted on June 17, 2021

Do we even need Compositional Layouts to design our next iOS app?

Apple has a great eye for design as can be seen from its clean, elegant and clutter free interfaces. They provide the developers, equal opportunity to create the best experience for the end users. Whether one wants a list layout like in the iPhone Contacts app or a grid layout like in the Instagram app, layout APIs such as UITableView and UICollectionView can be used as per the requirements. These APIs have existed for a long time; UITableView exists since iOS 2 (year 2008) and UICollectionView exists since iOS 6 (year 2012).

Introduction

For today, our focus will be to know about Collection View Layout and Advances in Collection View Layout.

UICollectionViewLayout, as per Apple’s definition, is a line-based system that allows you to lay out on the orthogonal axis, of the layout axis, until you just fill up the amount of space you have available and we drop to the next line.

UICollectionViewLayout helps us to achieve simple line-based layouts with little configuration and little customisation. In order to create advanced layouts, we need to subclass UICollectionViewFlowLayout or create our custom layout by subclassing UICollectionViewLayout directly. We have extensively used and continue to use this layout, but if we wish to have multiple scrolling sections and variably sized tiled layouts like in AppStore, it’s very difficult to do so with UICollectionViewFlowLayout.

In order to make things easier, Apple decided to make some advancements and upgraded the capabilities of UICollectionViewLayout. They introduced a layout API, which is capable of handling current day complex designs, and is known as Compositional Layouts. Compositional Layouts were introduced with iOS 13 in the year 2019.

Implementation

We are going to discuss and compare two approaches, the Traditional layout approach and the Compositional layout approach.

Let’s define our requirements first.

I would like to design an interface,

  1. Which scrolls vertically like a list,
  2. Has a header with each row, and
  3. Each row scrolls horizontally

Traditional Layout

As per our requirements, we can see we will have a design as below (1A).

  • We have the entire screen covered with UITableView to support vertical scrolling.
  • We place a combination of HeaderView and UITableViewCell in each UITableView section.
  • Each UITableViewCell will have a UICollectionView to support horizontal scrolling.
  • And each UICollectionView is comprised of multiple colourful UICollectionViewCell.

Traditional Layout Approach

The UITableView data source methods will look like the below code snippets (1B, 1C, 1D). We create class TraditionalVC and another class TableViewCell to have UICollectionView data source within UITableViewCell. In order to further modularise and customise the code, we will have to create a class HeaderCell and a class CollectionViewCell. But for now, we are keeping it simple and just use two classes for a basic implementation.

// 1B - UITableView data source in class TraditionalVC
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
    return cell
}
Enter fullscreen mode Exit fullscreen mode
// 1C - UITableView delegate in class TraditionalVC
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    guard let header = tableView.dequeueReusableCell(withIdentifier: "HeaderCell") else { return UITableViewCell() }
    return header
}
Enter fullscreen mode Exit fullscreen mode
// 1D - UICollectionView data source in class TableViewCell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath)
    return cell
}
Enter fullscreen mode Exit fullscreen mode

Compositional Layout

Now, let’s talk about Compositional layouts approach.

Compositional Layout Approach

From the above design (2A) and the below code snippet (2B), we can see there are 4 core components in this layout:

  1. Item: You can think about it as the UICollectionViewCell, which renders on the screen.

  2. Group: They’re used to describe the layout of our items within a section. We can provide groups with horizontal, vertical or even custom layouts within the sections.

  3. Section: It’s the same as sections in UICollectionView and UITableView. We can define the number of sections in UICollectionView data source and the items in each section.

  4. Layout: It’s the entire space, the elements occupy on the screen.

// 2B - Create Compositional Layout
func createLayout() {
    let size = NSCollectionLayoutSize(widthDimension: .absolute(250.0), heightDimension: .absolute(150.0))

    // Item
    let item = NSCollectionLayoutItem(layoutSize: size)

    // Group
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])

    // Section
    let section = NSCollectionLayoutSection(group: group)
    section.orthogonalScrollingBehavior = .continuous

    // Header in section
    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.99), heightDimension: .absolute(40.0))
    section.boundarySupplementaryItems = [.init(layoutSize: headerSize, elementKind: "HeaderKind", alignment: .topLeading)]

    // Layout
    collectionView.collectionViewLayout = UICollectionViewCompositionalLayout(section: section)
}
Enter fullscreen mode Exit fullscreen mode

We can also define layout size for each item and group. The size can be defined using:

  • .fractionalWidth: The value of .fractionalWidth ranges between 0 and 1. If .fractionalWidth is 0.5, then it will occupy 50% width.

  • .fractionalHeight: The value of .fractionalHeight ranges between 0 and 1. If .fractionalHeight is 1, then it will occupy 1x height.

NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0))
Enter fullscreen mode Exit fullscreen mode
  • .absolute: If we want to have a fixed value, we will use .absolute.
NSCollectionLayoutSize(widthDimension: .absolute(50.0), heightDimension: .absolute(50.0))
Enter fullscreen mode Exit fullscreen mode
  • .estimated: It is used with self-sizing groups and items. We can provide an estimated value. The actual value will be computed during layout.
NSCollectionLayoutSize(widthDimension: .estimated(50.0), heightDimension: .estimated(50.0))
Enter fullscreen mode Exit fullscreen mode

Comparison

This is a starter project to demonstrate the differences between Traditional layout approach and Compositional layout approach. It might seem that there are no significant differences between both the approaches. But the difference is realised,

  1. When we have more complex designs. So, if we modify our current requirements and wish to have a different layout in every section. For the Traditional approach, we will create new class for every UITableViewCell and handle multiple data sources. Whereas, in the Compositional layout, we can customise and add more sections (see 3A, 3B below), without the need to add new data sources.

  2. When we have more customisation. There are ways in which groups can handle different types of items. The sections can have different scrolling behaviors. Supplementary views to the sections can be created without the overhead of new UITableViewCell for HeaderCell.

  3. Compositional layout approach takes lesser implementation time as compared to Traditional layout approach for complex designs.

  4. Compositional layout requires lesser number of new classes and lesser lines of code.

  5. Compositional layout is easy to customise and maintain since the code is not distributed and nested in multiple classes and multiple data sources.

  6. Last but not the least, Compositional layouts will lead to developer happiness.

// 3A - Create Compositional Layout with multiple sections
func createLayout() {
    let layout = UICollectionViewCompositionalLayout { (section, env) -> NSCollectionLayoutSection? in
        switch section {
        case 0: return self.firstLayoutSection()
        case 1: return self.secondLayoutSection()
        default: return self.defaultLayoutSection()
        }
    }
    collectionView.collectionViewLayout = layout
}
Enter fullscreen mode Exit fullscreen mode
// 3B - Create a section with items and group
func firstLayoutSection() {
    let size = NSCollectionLayoutSize(widthDimension: .absolute(250.0), heightDimension: .absolute(150.0))

    // Item
    let item = NSCollectionLayoutItem(layoutSize: size)

    // Group
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])

    // Section
    let section = NSCollectionLayoutSection(group: group)
    section.orthogonalScrollingBehavior = .continuous

    // Header in section
    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.99), heightDimension: .absolute(40.0))
    section.boundarySupplementaryItems = [.init(layoutSize: headerSize, elementKind: "HeaderKind", alignment: .topLeading)]

    // Layout
    collectionView.collectionViewLayout = UICollectionViewCompositionalLayout(section: section)
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In conclusion, I would like to say that if we want to create complex designs and make our app stand out with creative layouts, UICompositionalLayout is the way to go.

I have linked a demo project here, which creates a layout as below. You can also have a look at WWDC 2019 Advances in Collection View layout session. Happy Coding! 😊

Demo project layout

💖 💪 🙅 🚩
stutid
Stuti Dobhal

Posted on June 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related