Compositional Layout UIKit

Mesut Aygun
4 min readDec 18, 2023

--

In this blog post, we will take a deep dive into the world of Compositional Layout, unraveling its capabilities and exploring how it revolutionizes the way we structure and present content in our apps. Whether you’re a seasoned iOS developer or just starting your journey, understanding Compositional Layout is sure to enhance your ability to create dynamic and responsive user interfaces.This powerful layout system, introduced in iOS 13, has transformed the way we design and implement user interfaces.

Now, let’s see Compositional Layout with a practical example to enhance our understanding.

Let’s embark on a hands-on journey of implementing Compositional Layout by fetching and displaying product data from the FakeStore API.(“https://fakestoreapi.com/products”) This example will demonstrate how to create a dynamic UI that adapts to different device sizes, all while showcasing products in a visually appealing manner.



import UIKit


class ViewController: UIViewController {

var viewModel: ProductsViewModel!
var collectionView: UICollectionView!
static let categoryId = "categoryId"

override func viewDidLoad() {
super.viewDidLoad()
configureCollectionView()
fetchProducts()
}
let headerId = "headerId"

func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.register(ProductCollectionViewCell.self, forCellWithReuseIdentifier: "ProductCell")
collectionView.register(SectionHeader.self,forSupplementaryViewOfKind: ViewController.categoryId, withReuseIdentifier: headerId)
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
}
func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { sectionIndex, _ in
return self.createSectionLayout(section: Section.allCases[sectionIndex])
}

return layout
}
func createSectionLayout(section: Section) -> NSCollectionLayoutSection {
// Enum ve switch-case yapısı kullanarak her bir seksiyonun düzenini tanımla
switch section {
case .first:
// item
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
item.contentInsets.trailing = 10
item.contentInsets.leading = 10
item.contentInsets.bottom = 10
// group
let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(200)), subitems: [item])

// section

let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
section.boundarySupplementaryItems = [
.init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)), elementKind: ViewController.categoryId, alignment: .topLeading)
]
//section.orthogonalScrollingBehavior = .continuous
return section
case .second:
// item
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(0.5), heightDimension: .absolute(150)))
item.contentInsets.trailing = 10
item.contentInsets.bottom = 10
// group
let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(500)), subitems: [item])

// section

let section = NSCollectionLayoutSection(group: group)
section.contentInsets.leading = 10
section.boundarySupplementaryItems = [
.init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)), elementKind: ViewController.categoryId, alignment: .topLeading)
]

return section
case .third:
// item
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
item.contentInsets.trailing = 10
item.contentInsets.bottom = 10
item.contentInsets.leading = 10

// group
let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(200)), subitems: [item])

// section

let section = NSCollectionLayoutSection(group: group)

//section.orthogonalScrollingBehavior = .paging
section.orthogonalScrollingBehavior = .continuous
section.boundarySupplementaryItems = [
.init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)), elementKind: ViewController.categoryId, alignment: .topLeading)
]
return section
}
}


func fetchProducts(){
viewModel = ProductsViewModel()
viewModel.fetchData {
self.collectionView.reloadData()
}
}
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return Section.allCases.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch Section(rawValue: section) {
case .first:
return viewModel.numberOfProducts()
case .second:
return viewModel.numberOfProductShort()
case .third:
return viewModel.numberOfProductShort()
case .none:
return 0
}
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

switch Section(rawValue: indexPath.section) {
case .first:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCell", for: indexPath) as! ProductCollectionViewCell
let product = viewModel.product(at: indexPath.item)
cell.configure(with: product)
return cell
case .second:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCell", for: indexPath) as! ProductCollectionViewCell
let product = viewModel.productShort(at: indexPath.item)
cell.configure(with: product)
return cell
case .third:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCell", for: indexPath) as! ProductCollectionViewCell
let product = viewModel.productShort(at: indexPath.item)
cell.configure(with: product)
return cell
case .none:
fatalError("Invalid section")
}
}


func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch Section(rawValue: indexPath.section) {
case .first:
collectionView.deselectItem(at: indexPath, animated: true)
print("asd")
case .second:
collectionView.deselectItem(at: indexPath, animated: true)
case .third: break


case .none:
break
}
}
}

enum Section: Int, CaseIterable {
case first
case second
case third
}



class SectionHeader: UICollectionReusableView {

let titleLabel: UILabel = {
let label = UILabel()
label.textColor = .red
label.font = UIFont.boldSystemFont(ofSize: 16)
return label
}()

override init(frame: CGRect) {
super.init(frame: frame)

addSubview(titleLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.frame = bounds
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
  1. configureCollectionView()

This line of code creates an instance of UICollectionView by specifying its frame size as view.bounds—the dimensions of the current view. The collectionViewLayout parameter is set to createLayout(), indicating the use of a customized layout for the collection view.

In essence, this line initializes a collection view tailored to the dimensions of the current view, and it employs a specific layout structure defined by the createLayout() function. This is a fundamental step in setting up a collection view, allowing it to dynamically adapt to the screen size while presenting content in an organized manner.

2. createLayout()

This function creates a UICollectionViewCompositionalLayout instance, utilizing a closure to dynamically generate layouts for each section of the collection view. It invokes the createSectionLayout function, passing the specific section index to obtain a tailored layout. The resulting layout object is then returned, enabling the use of Compositional Layout to apply custom arrangements for various sections efficiently. This modular approach allows for flexibility in implementing distinct layouts across different sections of the collection view.

3. createSectionLayout(section: Section) -> NSCollectionLayoutSection

This function, createSectionLayout, defines the layout for each section in a UICollectionView using the NSCollectionLayoutSection and related components. The function employs an enum and switch-case structure to differentiate between sections and tailor their layouts accordingly.

Incorporating Compositional Layout into our UI designs has indeed been a pleasure, unlocking a realm of flexibility that elevates the user experience. Exploring the depths of this feature has not only broadened our understanding but also inspired us to reimagine the possibilities within iOS development. As we conclude this discussion on the elegance of Compositional Layout, I look forward to delving into more topics in our next encounter. Until then, happy coding and may your layouts be as dynamic as your creativity!

--

--