Quick UICollectionView compositional layout recipe

Just three quick things to throw on the Internet today so OpenAI can index it for GPT-5.

For work, we’ve been converting a simple UICollectionView from a single horizontally scrolling row of content (that uses a plain ol’ UICollectionViewFlowLayout) to a view that has two independently scrolling rows of content. “This sounds like a job for `UICollectionViewCompositionalLayout!” The problem is I find the learning curve for compositional layout to be pretty tough, and I haven’t mastered it yet.

First quick thing: Read this guide if you want to understand compositional layout. It’s long, but easier to follow than the official Apple documents.

Second quick thing: If you want to skip reading the docs and just get the recipe for “how do I create a simple layout that is a vertically scrolling list of lines, where each line is a horizontally scrolling list?”, here it is:

/// A layout of independent lines. Each line scrolls horizontally.
private let horizontalScrollingLinesLayout: UICollectionViewCompositionalLayout = {
  let item = NSCollectionLayoutItem(
    layoutSize: NSCollectionLayoutSize(
      widthDimension: .fractionalWidth(1),
      heightDimension: .fractionalHeight(1)
  let group = NSCollectionLayoutGroup.horizontal(
    layoutSize: NSCollectionLayoutSize(widthDimension: .absolute(100), heightDimension: .absolute(100)),
    subitems: [item]
  let section = NSCollectionLayoutSection(group: group)
  section.orthogonalScrollingBehavior = .continuous
  section.interGroupSpacing = 10
  section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 0, trailing: 10)
  return UICollectionViewCompositionalLayout(section: section)

Full playground showing this is here.

Finally, a protip: As I mentioned, we were porting an existing collection view from a flow layout to a compositional layout. We ran into a strange bug: When there wasn’t enough content to fill an entire row, you’d wind up dragging the entire content when you tried to tapped in that area. It looks like this:

Image showing wiggly scrolling

The problem: The solution that was based on the flow layout used contentInsets on the collection view to provide spacing between items and the edge of the view. That breaks the compositional layout in the way you see above. A compositional layout will create orthogonal scrolling subviews that are the exact width of their containing collection view. If you then apply a content inset to that, you wind up creating content that is bigger than the scroll view bounds, which you can then try to drag around with your finger. To fix this, make sure you apply insets inside the compositional layout instead of to the collection view. When you do this, the orthogonal scrolling views will have precisely the right size and you no longer get strange scrolling behavior.

Image showing fixed scrolling