UICollectionView scrolling control using UICollectionViewCompositionalLayout

Hi 👋 Given I have a UICollectionView using a UICollectionViewCompositionalLayout. The layout contains of a single group containing 3 full-width items. The orthogonalScrollingBehavior behaviour of the section is set to .paging. The section includes a header in which a UISegmentedControl is presented.

Here's what I want to achieve:

  • User changes the segmented control: The value change (selectedIndex) of the segmented control alters the presented page in the collection view (currently via scrollToItem(at:at:animated:)).
  • User swipes the paging collection view: When the user swipes the pages, the segmentedControl has to be changed to reflect the current page

Here's my problem:

  • When I use the segmented control to navigate through the pages, the sections visibleItemsInvalidationHandler fires and updates the segmented control with (temporary) invalid values.

This causes some UI glitches in the segmented control. I can't rely on the UIScrollView delegate because the collection view seems to use a different scroll view for the paging mechanism internally.

Question

  • Is there a way to intercept the visibleItemsInvalidationHandler for the time the collection view animates the scrollToItem(at:at:animated:) changes?
  • Or is there any other alternative to the visibleItemsInvalidationHandler which is called only once after the transition is done?

Here's my layout setup:

private func makeCompositionalLayout() -> UICollectionViewLayout {
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .estimated(300)
    )
    let padding: CGFloat = 16
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    item.contentInsets = NSDirectionalEdgeInsets(
        top: 0,
        leading: padding,
        bottom: 0,
        trailing: padding
    )

    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
    let header = NSCollectionLayoutBoundarySupplementaryItem(
        layoutSize: headerSize,
        elementKind: MyCollectionReusableView.sectionHeaderElementKind,
        alignment: .top
    )

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

    let section = NSCollectionLayoutSection(group: group)
    section.orthogonalScrollingBehavior = .paging

    section.visibleItemsInvalidationHandler = { [weak self] _, contentOffset, _ in
        guard let self, let collectionView else { return }
        let pageWidth = collectionView.bounds.width
        let fractionalPage = contentOffset.x / pageWidth
        segmentedControlDelegate?.update(idx: lround(fractionalPage))
    }
    section.boundarySupplementaryItems = [header]
    return UICollectionViewCompositionalLayout(section: section)
}
UICollectionView scrolling control using UICollectionViewCompositionalLayout
 
 
Q