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,
- Which scrolls vertically like a list,
- Has a header with each row, and
- 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
andUITableViewCell
in eachUITableView
section. - Each
UITableViewCell
will have aUICollectionView
to support horizontal scrolling. - And each
UICollectionView
is comprised of multiple colourfulUICollectionViewCell
.
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
}
// 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
}
// 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
}
Compositional Layout
Now, let’s talk about Compositional layouts approach.
From the above design (2A) and the below code snippet (2B), we can see there are 4 core components in this layout:
Item: You can think about it as the
UICollectionViewCell
, which renders on the screen.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.
Section: It’s the same as sections in
UICollectionView
andUITableView
. We can define the number of sections inUICollectionView
data source and the items in each section.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)
}
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))
-
.absolute: If we want to have a fixed value, we will use
.absolute
.
NSCollectionLayoutSize(widthDimension: .absolute(50.0), heightDimension: .absolute(50.0))
- .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))
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,
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.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
forHeaderCell
.Compositional layout approach takes lesser implementation time as compared to Traditional layout approach for complex designs.
Compositional layout requires lesser number of new classes and lesser lines of code.
Compositional layout is easy to customise and maintain since the code is not distributed and nested in multiple classes and multiple data sources.
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
}
// 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)
}
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! 😊
Top comments (0)