UICollectionViewCompositionalLayout - center items in sections or groups
I found a solution for this, using
custom(layoutSize: NSCollectionLayoutSize, itemProvider: @escaping NSCollectionLayoutGroupCustomItemProvider)
I created the following extension, which calculates the frames of each element in the group, centring as many items as will fit on each row
extension NSCollectionLayoutGroup {
static func verticallyCentered(cellSizes: [CGSize], interItemSpacing: CGFloat = 10, interRowSpacing: CGFloat = 10) -> NSCollectionLayoutGroup {
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
return custom(layoutSize: groupSize) { environment in
var items: [NSCollectionLayoutGroupCustomItem] = []
var yPos: CGFloat = environment.container.contentInsets.top
var rowSizes: [CGSize] = []
func totalWidth() -> CGFloat {
rowSizes.map(\.width).reduce(0) {
$0 == 0 ? $1 : $0 + interItemSpacing + $1
}
}
func addRowItems() {
var xPos = (environment.container.effectiveContentSize.width - totalWidth())/2 + environment.container.contentInsets.leading
let maxItemHeight = rowSizes.map(\.height).max() ?? 0
let rowItems: [NSCollectionLayoutGroupCustomItem] = rowSizes.map {
let rect = CGRect(origin: CGPoint(x: xPos, y: yPos + (maxItemHeight - $0.height) / 2), size: $0)
xPos += ($0.width + interItemSpacing)
return NSCollectionLayoutGroupCustomItem(frame: rect)
}
items.append(contentsOf: rowItems)
}
for (index, cellSize) in cellSizes.enumerated() {
rowSizes.append(cellSize)
if totalWidth() > environment.container.effectiveContentSize.width {
rowSizes.removeLast()
addRowItems()
yPos += (cellSize.height + interRowSpacing)
rowSizes = [cellSize]
}
if index == cellSizes.count - 1 {
addRowItems()
}
}
return items
}
}
}
Then create a layout section with a single group that contains all the items in that section. First you'll first need to calculate the sizes of all the elements in the section, and then pass those in to the function above, something like this (note that I'm using a DiffableDataSource here)…
func someLayoutSection(sectionIndex: Int) -> NSCollectionLayoutSection {
let itemCount = collectionView.numberOfItems(inSection: sectionIndex)
let cell = SomeCell(frame: .zero)
let cellSizes: [CGSize] = (0..<itemCount).compactMap {
switch diffableDataSource.itemIdentifier(for: IndexPath(item: $0, section: sectionIndex)) {
case let .someItem(something):
cell.configure(thing: something)
return cell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
default:
return nil
}
}
let group = NSCollectionLayoutGroup.verticallyCentered(cellSizes: cellSizes)
group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20).scaled
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 0, bottom: 20, trailing: 0)
return section
}
and then create the layout…
func makeLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
let section = diffableDataSource.snapshot().sectionIdentifiers[sectionIndex]
switch section {
case .someSection:
return someLayoutSection(sectionIndex: sectionIndex)
case .otherSection:
// etc
}
}
UICollectionViewCompositionalLayout with groupPagingCentered doesn't start centered
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
}
Related Topics
Swift Uiview Opacity Programmatically
How to Convert Cgpoint in Nsvalue in Swift
Xcode UI Test Uikeyinput Typetext
Restkit Request Not Sending Parameters
Checking Cellular Network Type in iOS
React Native 0.40.0: Rctbundleurlprovider.H" File Not Found - Appdelegate.M
Form Nspredicate from String That Contains Id's
Cocoapods Page Redirecting to Github
Conversion from String to Date in Swift Returns Nil
Replacement for Arkit in iOS10
How to Lock Viewcontroller in Portrait Mode
Passing Arguments to @Selector Method
Cannot Call Value of Non-Function Type 'Module<Firebase>'
Using Shader Modifiers to Animate Texture in Scenekit Leads to Jittery Textures Over Time