Uicollectionviewcompositionallayout - Center Items in Sections or Groups

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



Leave a reply



Submit