Question
How can I get the full scrollable content width of a horizontal collection view section using Compositional Layout?
Details
I am implementing a UICollectionView
with Compositional Layout so I can get the nice and easy section.orthogonalScrollingBehavior = .groupPaging
behavior. However, I also need to know the percentage that the user has scrolled through the section so I can keep a custom PageControl in sync. With other collection views that use UICollectionViewFlowLayout
this was a straightforward calculation:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollProgress = scrollView.contentOffset.x / scrollView.contentSize.width
pageControl.currentPercentage = scrollProgress
I've already learned that I need to use visibleItemsInvalidationHandler
instead of this delegate method. My problem is that with Compositional Layout, the value of both layoutEnvironment.container.contentSize.width
and collectionView.contentSize.width
is no longer the full width of the scrollable content, it's now equal to the frame of the collectionView itself. The value of scrollOffset
is in terms of the full scrollable content size, though.
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, scrollOffset, layoutEnvironment in
guard let self = self else { return }
let contentWidth = layoutEnvironment.container.contentSize.width // not the full content width
//let contentWidth = self.collectionView.contentSize.width // neither is this!
let scrollProgress = scrollPosition / contentWidth
self.pageControl.currentPercentage = scrollProgress
}
I've tried digging around in layoutEnvironment
and found nothing equal to the full scrollable content size. I've also tried computing it based on the number of cells but I haven't been able to get it right because of needing to account for contentOffsets and inter-group spacing.
My full layout code and screenshots of the resulting UI is below, in case it helps. Maybe there's a way to change this layout to achieve the same result and get the right value in contentSize
.
let layout: UICollectionViewLayout = {
let itemWidth = itemSize.width - 2 * Const.margin
let itemLayoutSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemLayoutSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(itemWidth), heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: Const.margin, bottom: 0, trailing: Const.margin)
section.interGroupSpacing = Const.interItemSpacing
section.orthogonalScrollingBehavior = .groupPaging
section.visibleItemsInvalidationHandler = { /* ... */ }
return UICollectionViewCompositionalLayout(section: section)
}()
Starting position:
Scrolled to next item: