'UICollectionViewCompositionalLayout with groupPagingCentered doesn't start centered
My layout code is very simple, something that you will have seen in every tutorial or article about the new compositional layouts.
func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(top: 0, leading: 5, bottom: 0, trailing: 5)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPagingCentered
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
When I start the app, the cell is not properly centered. Only when I drag the cell by the tiniest amount, does it spring to its correct place.
Before:
After I drag it a tiny bit:
I have not seen any questions on SO about the same problem. Nobody on Twitter or in blogs talking about it. Not sure what I am doing wrong here?
Solution 1:[1]
Maybe too late, but here is a workaround:
func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
let sideInset: CGFloat = 5
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(top: 0, leading: sideInset, bottom: 0, trailing: sideInset)
let groupWidth = environment.container.contentSize.width * 0.93
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth), heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
// add leading and trailing insets to the section so groups are aligned to the center
let sectionSideInset = (environment.container.contentSize.width - groupWidth) / 2
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: sectionSideInset, bottom: 0, trailing: sectionSideInset)
// note this is not .groupPagingCentered
section.orthogonalScrollingBehavior = .groupPaging
return section
}
return layout
}
Solution 2:[2]
This is expected behavior. "centered" means a cell snaps to the center after scrolling. Before doing any scrolling, the whole group is scrolled all the way to the right. Your group is only 0.93 fractional width so the difference is slight. The effect is much less unsightly when the fractional width is smaller.
Solution 3:[3]
I found out that if you add Header to you section (even empty on) it solves the problem.
let layoutSectionHeaderSize = NSCollectionLayoutSize(widthDimension: .estimated(1.0), heightDimension: .estimated(1.0))
let layoutSectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: layoutSectionHeaderSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [layoutSectionHeader]
And yes, you need this:
register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "SectionHeader")
And provide any empty UICollectionReusableView here:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "SectionHeader", for: indexPath) as? SectionHeader else {
fatalError("Could not dequeue SectionHeader")
}
return // some empty SectionHeader
}
The working sample is here
Solution 4:[4]
Scroll to the first item:
@available(iOS 13.0, *)
private func centerCollectionView() {
guard collectionView.collectionViewLayout is UICollectionViewCompositionalLayout else { return }
collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
}
In case the item doesn’t fill the whole screen you will need to scroll to the others, otherwise there is a weird flash. So for instance, if your screen is filled with two items the code would be:
collectionView.scrollToItem(at: IndexPath(item: 1, section: 0), at: .centeredHorizontally, animated: false)
collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
Solution 5:[5]
In my case, my collectionView-embedded-viewController is embedded in a custom tab bar controller. So when my collectionView was swiped, I sent action to my parent viewController's tab bar button. And it worked.
class ChildVC: UIViewController {
var parentVC: MY_PARENT_VC?
var collectionView: UICollectionView!
// for the first horizontal row
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
parentVC?.MY_CUSTOM_BUTTON.sendActions(for: .touchUpInside)
}
}
extension ChildVC: UICollectionViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
parentVC?.MY_CUSTOM_BUTTON.sendActions(for: .touchUpInside)
}
}
Solution 6:[6]
Easy Fix
As suggested in another answer, you can tell the collection view to scroll a certain item to the center. If you do this after the view is visible, the user will see UI moving on its own...not ideal. You can put the item centering code into viewDidLoad but it won't work unless you set a very slight delay - I assume you just need to center the cell after the initial layout pass has completed. The code below is working for me:
override func viewDidLoad() {
super.viewDidLoad()
// collectionView setup goes here...
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) { [self] in
collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
}
}
Solution 7:[7]
Even later, but I had a similar issue trying to do something similar and this line magically works:
section.contentInsetsReference = .readableContent
I have literally no idea why it works. I tried using item.contentInsets, edgeSpacing, everything. Magically this just worked for me. Maybe you'll have similar success?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Kevin Renskers |
| Solution 2 | matt |
| Solution 3 | dollar2048 |
| Solution 4 | |
| Solution 5 | user16091991 |
| Solution 6 | Trev14 |
| Solution 7 | nickneedsaname |


