Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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:

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

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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())
        
        configureDataSource()
        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() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        let collectionView = List {
            ListItem("Cell 1")
            ListItem("Cell 2")
            ListItem("Cell 3")
            ListItem("paperplane.fill")
            ListItem("doc.text")
            ListItem("book.fill")
        }
        collectionView.frame = view.bounds
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(collectionView)
    }   
}

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading