You are currently viewing How to Create Custom Header & Footer Using UIHostingConfiguration

How to Create Custom Header & Footer Using UIHostingConfiguration

When Apple introduces UIHostingConfiguration in WWDC, they mainly focus on showcasing how we can use it to create custom cells for table and collection view. This makes me wonder if it is possible to create a custom header and footer using UIHostingConfiguration.

After a few hours of poking around, it turns out to be totally viable! And the way to make that happen is surprisingly simple. In fact, features such as data binding, animation, and self-resizing all work like a charm on the custom header and footer created using UIHostingConfiguration.

These are some exciting findings and I can’t wait to share them with you, so let’s get right into it!


Creating the Custom Header and Footer

Supplementary Views Registration

In order to display a custom header and footer, we must first register the custom header and footer to the collection view. Thus let’s go ahead and define the registration objects.

private var headerRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewCell>!
private var footerRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewCell>!

Notice that we are using UICollectionViewCell as the supplementary view type. Doing so will enable us to define the header and footer using UIHostingConfiguration when initializing the registration objects.

// Custom header registration
headerRegistration = .init(elementKind: UICollectionView.elementKindSectionHeader) { [unowned self]
    (header, elementKind, indexPath) in

    // Define header content using `UIHostingConfiguration`
    header.contentConfiguration = UIHostingConfiguration {
        Text("Header")
            .font(.title)
    }
}

// Custom footer registration
footerRegistration = .init(elementKind: UICollectionView.elementKindSectionFooter) { [unowned self]
    (footer, elementKind, indexPath) in
    
    // Define footer content using `UIHostingConfiguration`
    footer.contentConfiguration = UIHostingConfiguration {
        Text("Footer")
            .font(.title3)
    }
}

Both headerRegistration and footerRegistration are initialized using the same initializer, just make sure to pass in the correct elementKind, then you are good to go.

Dequeuing the Supplementary Views

With both of the registration objects in place, we can go ahead and dequeue the custom header and footer. We can do that in the collectionView(_:viewForSupplementaryElementOfKind:at:) data source method.

func collectionView(_ collectionView: UICollectionView,
                    viewForSupplementaryElementOfKind elementKind: String,
                    at indexPath: IndexPath) -> UICollectionReusableView {
    
    switch elementKind {
    case UICollectionView.elementKindSectionHeader:
        // Dequeue header view using `headerRegistration`
        let header = collectionView.dequeueConfiguredReusableSupplementary(
            using: headerRegistration,
            for: indexPath
        )
        return header
        
    case UICollectionView.elementKindSectionFooter:
        // Dequeue footer view using `footerRegistration`
        let footer = collectionView.dequeueConfiguredReusableSupplementary(
            using: footerRegistration,
            for: indexPath
        )
        return footer
        
    default:
        fatalError("Unexpected element kind")
    }
}

Do take note that the elementKind here must match with the elementKind that we pass in during the registration object initialization. Otherwise, we will get an NSInternalInconsistencyException with reason:

The view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: does not match the element kind it is being used for.

Enabling Header & Footer in Compositional Layout

The last piece of the puzzle is to configure the collection view’s layout to show a header and footer.

Here’s how to do it if you are using a compositional list layout:

var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
layoutConfig.headerMode = .supplementary
layoutConfig.footerMode = .supplementary
let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)

For situation where you are building a custom layout, you can do it like this:

let headerFooterSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1.0),
    heightDimension: .estimated(44)
)

let header = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: headerFooterSize,
    elementKind: UICollectionView.elementKindSectionHeader,
    alignment: .top
)

let footer = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: headerFooterSize,
    elementKind: UICollectionView.elementKindSectionFooter,
    alignment: .bottom
)

let config = UICollectionViewCompositionalLayoutConfiguration()
config.boundarySupplementaryItems = [header, footer]

There you have it, that’s all it takes to create a custom header and footer using UIHostingConfiguration. Here’s a screenshot of the header and footer we just created.

Custom header & footer created using UIHostingConfiguration
A simple header and footer

Up for a Challenge?

At this stage, we can basically create any kind of layout and interaction as long as it is achievable using SwiftUI. Here’s a layout with custom header, footer and cell I build purely using UIHostingConfiguration and SwiftUI.

Custom header & footer build UIHostingConfiguration and SwiftUI

Do you think you can build one by yourself?

Here are a few tips to get you started:

  1. You can use the concept presented in this article to create a custom cell.
  2. Use the cell’s configurationUpdateHandler to highlight the selected cell with a red border. You can find out more here.
  3. Use the SwiftUI data binding technique to update the footer with the selected symbol name. More on that here.
  4. The header height change animation is just a basic SwiftUI transition animation, no extra work needs to be done thanks to the self-resizing nature of UICollectionViewCell in iOS 16.

Don’t worry if you somehow get stuck. You can always refer to the full sample code here on Github.


I hope you enjoy reading this article, if you do, feel free to follow me on Twitter and subscribe to my monthly newsletter so that you won’t miss out on any of my upcoming iOS development-related articles.

Thanks for reading. 👨🏻‍💻


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.