Search code examples
iosuikituicollectionviewcellsidebaruicollectionviewcompositionallayout

How to style a UICollectionViewCompositionalLayout header to be separated from its children list .insetGrouped layout?


I'm trying to mimic the appearance we find in the Mail app where the group header for mailboxes is collapsible and its children list is styled with the .insetGrouped layoutOption.

I tried to use UICollectionLayoutListConfiguration(appearance: .sidebarPlain) as the configuration but I didn't find a way to style the .insetGrouped appearance for the sections

Mail App:

Mail app sidebar appearance

The closest I got to this layout was this:

My app sidebar appearance

I'm using the approach below to configure the collectionView inside my ViewController.

let mixedListLayout = 
    UICollectionViewCompositionalLayout { section, env -> NSCollectionLayoutSection? in
    
    let listConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)

    return NSCollectionLayoutSection.list(using: listConfig, layoutEnvironment: env)
}

collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: mixedListLayout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]       
collectionView.delegate = self

view.addSubview(collectionView)

For the cell registration my approach is something like this:

let headerRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, SidebarItem> { (cell, _, item) in

    var contentConfiguration = UIListContentConfiguration.sidebarHeader()
    contentConfiguration.text = item.title
    cell.contentConfiguration = contentConfiguration
    let outlineDisclosureConfig = UICellAccessory.OutlineDisclosureOptions(style: .header)
    cell.accessories = [.outlineDisclosure(displayed: .always, options: outlineDisclosureConfig)]
}

let primaryRowRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, SidebarItem> { (cell, _, item) in

    var contentConfiguration = UIListContentConfiguration.sidebarCell()
    contentConfiguration.text = item.title
    contentConfiguration.image = item.image

    cell.contentConfiguration = contentConfiguration
    cell.indentationLevel = 0
    if let acessoryLabel = item.subtitle {
        cell.accessories = [.label(text: acessoryLabel)]
    }
    cell.accessories.append(.disclosureIndicator())
}

dataSource = UICollectionViewDiffableDataSource<SidebarSection, SidebarItem>(collectionView: collectionView) {
    (collectionView, indexPath, item) -> UICollectionViewCell in

    switch item.type {
    case .header:
        return collectionView.dequeueConfiguredReusableCell(using: headerRegistration, for: indexPath, item: item)
    case .primaryRow:
        return collectionView.dequeueConfiguredReusableCell(using: primaryRowRegistration, for: indexPath, item: item)
    }
}

Solution

  • After some more readings on documentation I've solved it by setting the headerMode to .firstItemInSection in the UICollectionLayoutListConfiguration so my mixedListLayout looks like this:

    let mixedListLayout = 
        UICollectionViewCompositionalLayout { section, env -> NSCollectionLayoutSection? in
        
        var listConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        
        listConfig.headerMode = section == 0 ? .none : .firstItemInSection // Using "first
    
        return NSCollectionLayoutSection.list(using: listConfig, layoutEnvironment: env)
    }