How to Build Adaptive Collection Views in iOS with UITraitCollection and Compositional Layout

Build adaptive collection views in iOS using UITraitCollection for size class detection and UICollectionViewCompositionalLayout for flexible multi-section layouts. UITraitCollection provides device and environment traits including horizontal and vertical size classes. UICollectionViewCompositionalLayout lets you define different layouts per section with fractional sizing, orthogonal scrolling, and supplementary views. UIKit introduced Compositional Layout in iOS 13.

How to Use UITraitCollection for Adaptive Layouts

UITraitCollection encapsulates environment traits like size class, display scale, and user interface idiom. Access it through any UIViewController or UIView instance.

let horizontalSizeClass = traitCollection.horizontalSizeClass
let verticalSizeClass = traitCollection.verticalSizeClass

iOS defines two size class values: .compact represents constrained space like iPhone portrait width, and .regular represents expansive space like iPad width. The four combinations map to common device configurations. Compact width with Regular height represents iPhone portrait. Compact width with Compact height represents iPhone landscape. Regular width with Regular height represents iPad full screen. Regular width with Compact height represents iPad split view.

Apple recommends size classes over checking UIDevice.current.userInterfaceIdiom. Size classes respond to orientation changes and multitasking without additional code.

How to Adapt Flow Layout Cells with Size Classes

Implement UICollectionViewDelegateFlowLayout and provide sizeForItemAt based on trait collection values. This changes cell dimensions dynamically when the environment changes.

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAt indexPath: IndexPath
    ) -> CGSize {
        let isCompact = traitCollection.horizontalSizeClass == .compact
        let sideLength: CGFloat = isCompact ? 80 : 128
        return CGSize(width: sideLength, height: sideLength)
    }
}

Set the collection view's delegate to self. Call collectionViewLayout.invalidateLayout() in traitCollectionDidChange to trigger recalculation after environment changes.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    collectionView.collectionViewLayout.invalidateLayout()
}

What Is UICollectionViewCompositional Layout and How Does It Work?

UICollectionViewCompositionalLayout builds layouts from three primitives. NSCollectionLayoutItem is the smallest element representing a single cell. NSCollectionLayoutGroup arranges items horizontally, vertically, or in custom layouts. NSCollectionLayoutSection groups items into a section with inter-group spacing and supplementary views.

let itemSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(0.5),
    heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)

let groupSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1.0),
    heightDimension: .absolute(100)
)
let group = NSCollectionLayoutGroup.horizontal(
    layoutSize: groupSize,
    subitems: [item]
)

let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
collectionView.collectionViewLayout = layout

Fractional dimensions make the layout responsive: .fractionalWidth(0.5) creates a two-column grid that adapts to any container width. The .absolute option sets fixed point dimensions, and .estimated sets dimensions that the system measures at runtime.

How to Build Multi-Section Layouts with Different Grid Configurations

Use the section provider initializer to return different NSCollectionLayoutSection objects for each section index. This enables each section to have unique column counts, spacing, and scrolling behavior.

let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
    let columnCount = sectionIndex == 0 ? 1 : (sectionIndex == 1 ? 2 : 4)

    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0 / CGFloat(columnCount)),
        heightDimension: .fractionalHeight(1.0)
    )
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)

    let groupSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .absolute(80)
    )
    let group = NSCollectionLayoutGroup.horizontal(
        layoutSize: groupSize,
        subitems: [item]
    )

    let section = NSCollectionLayoutSection(group: group)
    section.interGroupSpacing = 10
    return section
}

The layoutEnvironment parameter provides access to the container size and trait collection. Use layoutEnvironment.container.contentSize.width for section-level adaptive decisions based on available width.

How to Add Orthogonal Scrolling in Compositional Layout

Set orthogonalScrollingBehavior on a section to scroll it horizontally while the rest of the collection view scrolls vertically.

section.orthogonalScrollingBehavior = .continuous

Available behaviors include .none, .continuous, .paging, .groupPaging, and .groupPagingCentered. Continuous creates a smooth horizontal scroll. Paging snaps to section boundaries. GroupPaging snaps to group boundaries.

How to Add Section Headers with Compositional Layout

Create NSCollectionLayoutBoundarySupplementaryItem for section headers. Register a UICollectionReusableView subclass for the supplementary element.

let headerSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1.0),
    heightDimension: .estimated(44)
)
let header = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: headerSize,
    elementKind: UICollectionView.elementKindSectionHeader,
    alignment: .top
)
section.boundarySupplementaryItems = [header]

Register the header view class and provide it in the diffable data source supplementaryViewProvider.

collectionView.register(
    SectionHeaderView.self,
    forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
    withReuseIdentifier: "SectionHeader"
)

dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
    let header = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: "SectionHeader",
        for: indexPath
    ) as! SectionHeaderView
    header.titleLabel.text = Section(rawValue: indexPath.section)?.name
    return header
}

Practical Tips for Adaptive Collection Views

Use fractional dimensions for responsive layouts. FractionalWidth and fractionalHeight adapt to the container automatically without manual calculations.

Call invalidateLayout in traitCollectionDidChange. This forces the layout to recalculate when the size class changes due to rotation or multitasking.

Add content insets at the item level for spacing within groups. Use section interGroupSpacing for spacing between groups.

Combine Compositional Layout with Diffable Data Source. The snapshot system provides automatic animations when data changes or sections reorder.

Test on iPad Simulator with different split view configurations. Size classes change dynamically in multitasking, and your layout must handle all combinations.

Summary

This tutorial covered the complete implementation process from setup to working code. You now have a functional implementation that you can extend and customize for your own projects. The same patterns apply to related features in the iOS ecosystem.

Frequently Asked Questions

Can I test this on the iOS Simulator? Some features require a physical device with a compatible processor. The article notes specific sections where the Simulator is sufficient and where a real device is required.

What iOS version is required? This tutorial targets iOS 13. Most of the APIs shown work on earlier versions as well, but some newer APIs may require the version specified in the article.

What are the prerequisites for this tutorial? You need Xcode installed on a Mac and basic familiarity with Swift programming. No prior experience with the specific framework is required.

Can I use this with SwiftUI instead of UIKit? The article demonstrates the UIKit approach. SwiftUI has its own APIs for similar functionality. You can adapt the concepts, but the implementation details differ between the two frameworks.

All articles