UICollectionView in SwiftUI — reusable component with UICollectionViewCompositionalLayout and UICollectionViewDiffableDataSource Part 2/3

Michał Ziobro
3 min readMay 3, 2020

In part 1 of this series of tutorials we’ve implemented generic CollectionViewController class in UIKit that is backing our SwiftUI component to empowers it with UICollectionViewCompositionalLayout and UICollectionViewDiffableDataSource. In this part of tutorial we focus on SwiftUI wrapper around this CollectionViewController. We implement CollectionView component.

1. How to bring UIKit controller to live in SwiftUI?

It is relatively easy to bring UIKit views and view controllers to SwiftUI. We have at our disposal two protocols that we need to implement, i.e. UIViewRepresentable or UIViewControllerRepresentable. Here we will use the second one as we have CollectionViewController which is subclass of UIViewController. I’ve tries approach with wrapping UICollectionView directly but I think it was a path that lead astray.

2. CollectionView properties and init

We need to start with defining CollectionView properties and constructor

CollectionView will be also generic type that is parametrized with Section and Item type. It defines following properties to customize behaviour of collection view:

  • layout to provide arbitrary UICollectionViewLayout including compositional layout definition
  • sections list of generic type [Section]
  • items dictionary of generic type [Section: [Items]]
  • supplementaryKinds to provide kinds of supplementary views like “header”, “footer”, etc.
  • animateChanges properties to explicitly define wether we want to animate datasource changes.

We have also defined two actions as params

  • snapshot which is closure that returns new snapshot of data to apply it to collection view data source.
  • content closure to provide AnyView content for given cell in collection view
  • supplementaryContent closure to provide AnyView content for given supplementary view in collection view

3. Implementing UIViewControllerRepresentable protocol

In this steps we need to implement 3 methods from UIViewControllerRepresentable protocol

  • makeUIViewController()
  • updateUIViewController()
  • makeCoordinator()

Coordinator is class that inherits from NSObject and is used to implement UICollectionViewDelegate protocol i.e. we assign it as collection view delegate object. It is needed cause our SwiftUI component is immutable struct and value type.

In makeUIViewController() we instantiate CollectionViewController and fill its input properties with layout, content closure, supplementaries, we provide also snapshot of data we want to display, finally we set CollectionView.Coordinator as delegate object to listen for item taps.

Each time when SwiftUI view is updated the updateUIViewController method is called, then we need to get new snapshot of data and apply it to out UICollectionViewDiffableDataSource to reload our UICollectionView with new cells. Here we also assign datasource from CollectionViewController to Coordinator it will enable as to better discover items for indexPaths in delegate methods implementations.

After assigning new snapshot of data we reloadDataSource and apply it. Here we are deciding whether we want to animate changes. Animations are nice but if we have tons of data cells it may be to slow to calculate difference as it has O(n) complexity. So we check how much cells we have in CollectionView and if it is reasonable small we can implicitly animate changes, otherwise we apply new snapshot without animations (reloading entire UICollectionView, not individual cells). I use here helper function to count cells

func smallItemsCount() -> Bool {
items.reduce(0) { (res, items) in
res + items.1.count
} < 1000
}

4. Creating new data snapshot

With each CollectionView update and new data (section, items) provided we want to apply this data to our UICollectionViewDiffableDataSource. We need to create NSDiffableDataSourceSnapshot.

In above code we just create new empty snapshot. Then append all sections. And then for each section we append new items.

5. Coordinator to implement UICollectionViewDelegate

Sometimes we want to listen for events in UICollectionView like didSelectItem.

Coordinator inherits from NSObject, and implements UICollectionViewDelegate. It takes as constructor param parent CollectionView (it is common patter writing SwiftUI wrappers that enable access to properties of SwiftUI component).

Here we implement just one method from UICollectionViewDelegate i.e.

collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)

and there instead of accessing section/items from parent.items/parent.sections we use dataSource. It is much safer approach that should always return correct item from underlying dataSource that has already been applied to UICollectionView.

let item = dataSource?.itemIdentifier(for: indexPath)

6. Where to go from here?

As this tutorial seems to be getting a bit long. I’ve split it into 3 parts.
1. CollectionViewController in UIKit
2. CollectionView in SwiftUI wrapping CollectionViewController
3. Using CollectionView component in SwiftUI code and compositional layouts definition

Full up to date code you can find on my GitHub
https://github.com/michzio/SwifUICollectionView

--

--