How can i use UICollectionView using @resultbuilder in swift?

I want to use @resultbuilder to create my own declarative UICollectionView in UIKit, similiar to what we get with List {} in SwiftUI.

My @resultBuilder to create a Snapshot looks like this:

struct SnapshotBuilder {
    static func buildBlock(_ components: ListItemGroup...) -> [ListItem] {
        return components.flatMap { $0.items }
    static func buildFinalResult(_ component: [ListItem]) -> NSDiffableDataSourceSectionSnapshot<ListItem> {
        var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<ListItem>()
        return sectionSnapshot

I also need to use the following extensions to pass ListItemGroup to SnapshotBuilder and get [ListItem]

struct ListItem: Hashable {
    let title: String
    let image: UIImage?
    var children: [ListItem]
    init(_ title: String, children: [ListItem] = []) {
        self.title = title
        self.image = UIImage(systemName: title)
        self.children = children

protocol ListItemGroup {
    var items: [ListItem] { get }

extension Array: ListItemGroup where Element == ListItem {
    var items: [ListItem] { self }

extension ListItem: ListItemGroup {
    var items: [ListItem] { [self] }

My List class looks like this:

final class List: UICollectionView {
    private enum Section {
        case main
    private var data: UICollectionViewDiffableDataSource<Section, ListItem>!
    init(@SnapshotBuilder snapshot: () -> NSDiffableDataSourceSectionSnapshot<ListItem>) {
        super.init(frame: .zero, collectionViewLayout: List.createLayout())
        data.apply(snapshot(), to: .main)
    required init(coder: NSCoder) {
        super.init(coder: coder)!
    private static func createLayout() -> UICollectionViewLayout {
        let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        return UICollectionViewCompositionalLayout.list(using: layoutConfig)
    private func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, ListItem> {
            (cell, indexPath, item) in
            var content = cell.defaultContentConfiguration()
            content.image = item.image
            content.text = item.title
        data = UICollectionViewDiffableDataSource<Section, ListItem>(collectionView: self) {
            (collectionView: UICollectionView, indexPath: IndexPath, identifier: ListItem) -> UICollectionViewCell? in
            let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
            cell.accessories = [.disclosureIndicator()]
            return cell

And i am using the List like this

class DeclarativeViewController: UIViewController {
    override func viewDidLoad() {
        view.backgroundColor = .systemBackground
        let collectionView = List {
            ListItem("Cell 1")
            ListItem("Cell 2")
            ListItem("Cell 3")
        collectionView.frame = view.bounds
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

It seems to work. I get the cell rows based on data count, but the cells are emtpy, like this:

enter image description here

What do i need to change to get this working? Where is my mistake? Can i define a dataSource within UICollectionView? Thanks for your answer.

>Solution :

You forgot to apply the configuration.

var content = cell.defaultContentConfiguration()
content.image = item.image
content.text = item.title
cell.contentConfiguration = content // this is important

Since UIListContentConfiguration is a struct, when you do var content = cell.defaultContentConfiguration() you just get a copy of it.

Leave a Reply