Reputation: 133
I set up a collection view and its layout with the new compositional stuff and it has been pretty cool, it seems to be very expandable but I can't find a way to make items centered in the collection, which is IMO a basic feature that one would imagine being supported..
What I have is:
Accomplished with this code:
UICollectionViewCompositionalLayout { section, env in
let tagDefaultSize = CGSize(width: 100, height: 40)
let item = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .estimated(tagDefaultSize.width), heightDimension: .absolute(tagDefaultSize.height))
)
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
leading: .fixed(0),
top: .fixed(0),
trailing: .fixed(8),
bottom: .fixed(0)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(tagDefaultSize.height)),
subitems: [item]
)
group.edgeSpacing = NSCollectionLayoutEdgeSpacing(
leading: .flexible(8),
top: .fixed(0),
trailing: .fixed(8),
bottom: .fixed(8)
)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(
top: 30,
leading: 20,
bottom: 40,
trailing: 20
)
return section
}
But what I need is this:
Has anyone had any luck with this? Thanks
Upvotes: 6
Views: 2411
Reputation: 53181
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
}
}
Upvotes: 9