我有UICollectionView
个随机单元格。有没有什么方法可以让我居中行?
这是默认情况下的显示方式:
[ x x x x x x ]
[ x x x x x x ]
[ x x ]
以下是所需的布局:
[ x x x x x ]
[ x x x x x ]
[ x x ]
答案 0 :(得分:44)
我必须做这样的事情,但需要一部分中的所有细胞。将UICollectionViewFlowLayout
扩展到中心单元格非常简单。我做了一个吊舱:
https://github.com/keighl/KTCenterFlowLayout
答案 1 :(得分:23)
首先是一些背景 - UICollectionView
与UICollectionViewLayout
结合,确定单元格在视图中的放置方式。这意味着集合视图非常灵活(您可以使用它创建几乎任何布局),但也意味着修改布局可能会有点混乱。
创建一个全新的布局类很复杂,所以你想尝试修改默认布局(UICollectionViewFlowLayout
)以使你的中心对齐。为了使其更简单,您可能希望避免对流程布局本身进行子类化。
这是一种方法(它可能不是最好的方法,但它是我能想到的第一种方法) - 将您的单元格分成两部分,如下所示:
[ x x x x x ] <-- Section 1
[ x x x x x ] <-- Section 1
[ x x ] <-- Section 2
如果你知道滚动视图的宽度和每行中可以容纳的单元格数,这应该是相当简单的。
然后,使用collectionView:layout:insetForSectionAtIndex:
委托方法设置第二部分的边距,使其看起来垂直居中。完成此操作后,您只需确保重新计算相应的部分/插图,以便支持纵向和横向方向。
这里有一个类似的问题 - How to center align the cells of a UICollectionView? - 有关插入方法的详细信息,尽管它并不是在尝试做同样的事情。
答案 2 :(得分:4)
使用Swift 4.1和iOS 11,根据您的需要,您可以选择 2以下完整实施之一来解决您的问题。
UICollectionViewCell
s固定大小下面的实现显示了如何使用UICollectionViewLayout
的{{3}}和UICollectionViewFlowLayout
的{{3}}来集中UICollectionView
的单元格:< / p>
CollectionViewController.swift
import UIKit
class CollectionViewController: UICollectionViewController {
let columnLayout = FlowLayout(
itemSize: CGSize(width: 140, height: 140),
minimumInteritemSpacing: 10,
minimumLineSpacing: 10,
sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
)
override func viewDidLoad() {
super.viewDidLoad()
title = "Center cells"
collectionView?.collectionViewLayout = columnLayout
collectionView?.contentInsetAdjustmentBehavior = .always
collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
return cell
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout()
super.viewWillTransition(to: size, with: coordinator)
}
}
FlowLayout.swift
import UIKit
class FlowLayout: UICollectionViewFlowLayout {
required init(itemSize: CGSize, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
super.init()
self.itemSize = itemSize
self.minimumInteritemSpacing = minimumInteritemSpacing
self.minimumLineSpacing = minimumLineSpacing
self.sectionInset = sectionInset
sectionInsetReference = .fromSafeArea
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
guard scrollDirection == .vertical else { return layoutAttributes }
// Filter attributes to compute only cell attributes
let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
// Group cell attributes by row (cells with same vertical center) and loop on those groups
for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
// Get the total width of the cells on the same row
let cellsTotalWidth = attributes.reduce(CGFloat(0)) { (partialWidth, attribute) -> CGFloat in
partialWidth + attribute.size.width
}
// Calculate the initial left inset
let totalInset = collectionView!.safeAreaLayoutGuide.layoutFrame.width - cellsTotalWidth - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(attributes.count - 1)
var leftInset = (totalInset / 2 * 10).rounded(.down) / 10 + sectionInset.left
// Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
for attribute in attributes {
attribute.frame.origin.x = leftInset
leftInset = attribute.frame.maxX + minimumInteritemSpacing
}
}
return layoutAttributes
}
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .cyan
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
预期结果:
layoutAttributesForElements(in:)
UICollectionViewCell
s 以下实施说明如何使用UICollectionViewLayout
的{{3}},UICollectionViewFlowLayout
的{{3}}和UILabel
的{{3}}按顺序排列使UICollectionView
的单元格居中:
CollectionViewController.swift
import UIKit
class CollectionViewController: UICollectionViewController {
let array = ["1", "1 2", "1 2 3 4 5 6 7 8", "1 2 3 4 5 6 7 8 9 10 11", "1 2 3", "1 2 3 4", "1 2 3 4 5 6", "1 2 3 4 5 6 7 8 9 10", "1 2 3 4", "1 2 3 4 5 6 7", "1 2 3 4 5 6 7 8 9", "1", "1 2 3 4 5", "1", "1 2 3 4 5 6"]
let columnLayout = FlowLayout(
minimumInteritemSpacing: 10,
minimumLineSpacing: 10,
sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
)
override func viewDidLoad() {
super.viewDidLoad()
title = "Center cells"
collectionView?.collectionViewLayout = columnLayout
collectionView?.contentInsetAdjustmentBehavior = .always
collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.label.text = array[indexPath.row]
return cell
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout()
super.viewWillTransition(to: size, with: coordinator)
}
}
FlowLayout.swift
import UIKit
class FlowLayout: UICollectionViewFlowLayout {
required init(minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
super.init()
estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
self.minimumInteritemSpacing = minimumInteritemSpacing
self.minimumLineSpacing = minimumLineSpacing
self.sectionInset = sectionInset
sectionInsetReference = .fromSafeArea
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
guard scrollDirection == .vertical else { return layoutAttributes }
// Filter attributes to compute only cell attributes
let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
// Group cell attributes by row (cells with same vertical center) and loop on those groups
for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
// Get the total width of the cells on the same row
let cellsTotalWidth = attributes.reduce(CGFloat(0)) { (partialWidth, attribute) -> CGFloat in
partialWidth + attribute.size.width
}
// Calculate the initial left inset
let totalInset = collectionView!.safeAreaLayoutGuide.layoutFrame.width - cellsTotalWidth - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(attributes.count - 1)
var leftInset = (totalInset / 2 * 10).rounded(.down) / 10 + sectionInset.left
// Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
for attribute in attributes {
attribute.frame.origin.x = leftInset
leftInset = attribute.frame.maxX + minimumInteritemSpacing
}
}
return layoutAttributes
}
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .orange
label.preferredMaxLayoutWidth = 120
label.numberOfLines = 0
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
contentView.layoutMarginsGuide.topAnchor.constraint(equalTo: label.topAnchor).isActive = true
contentView.layoutMarginsGuide.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
预期结果:
答案 3 :(得分:2)
如果任何人有一个具有2列的CollectionView,并且项目数是奇数,则最后一项应该居中对齐。然后使用此
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attribute in attributes) {
NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView
numberOfItemsInSection:attribute.indexPath.section];
if (itemCount % 2 == 1 && attribute.indexPath.item == itemCount - 1) {
CGRect originalFrame = attribute.frame;
attribute.frame = CGRectMake(self.collectionView.bounds.size.width/2-originalFrame.size.width/2,
originalFrame.origin.y,
originalFrame.size.width,
originalFrame.size.height);
}
}
return attributes;
}
答案 4 :(得分:2)
这可以通过(相对)简单的自定义布局实现,子布局来自UICollectionViewFlowLayout
。以下是 Swift
:
/**
* A simple `UICollectionViewFlowLayout` subclass that would make sure the items are center-aligned in the collection view, when scrolling vertically.
*/
class UICollectionViewFlowCenterLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let suggestedAttributes = super.layoutAttributesForElementsInRect(rect) else { return nil }
guard scrollDirection == .Vertical else { return suggestedAttributes }
var newAttributes: [UICollectionViewLayoutAttributes] = []
/// We will collect items for each row in this array
var currentRowAttributes: [UICollectionViewLayoutAttributes] = []
/// We will use this variable to detect new rows when iterating over items
var yOffset:CGFloat = sectionInset.top
for attributes in suggestedAttributes {
/// If we happen to run into a new row...
if attributes.frame.origin.y != yOffset {
/*
* Update layout of all items in the previous row and add them to the resulting array
*/
centerSingleRowWithItemsAttributes(¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
/*
* Reset the accumulated values for the new row
*/
currentRowAttributes = []
yOffset = attributes.frame.origin.y
}
currentRowAttributes += [attributes]
}
/*
* Update the layout of the last row.
*/
centerSingleRowWithItemsAttributes(¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
return newAttributes
}
/**
Updates the attributes for items, so that they are center-aligned in the given rect.
- parameter attributes: Attributes of the items
- parameter rect: Bounding rect
*/
private func centerSingleRowWithItemsAttributes(inout attributes: [UICollectionViewLayoutAttributes], rect: CGRect) {
guard let item = attributes.last else { return }
let itemsCount = CGFloat(attributes.count)
let sideInsets = rect.width - (item.frame.width * itemsCount) - (minimumInteritemSpacing * (itemsCount - 1))
var leftOffset = sideInsets / 2
for attribute in attributes {
attribute.frame.origin.x = leftOffset
leftOffset += attribute.frame.width + minimumInteritemSpacing
}
}
}
答案 5 :(得分:2)
我已经进行了子类UICollectionViewFlowLayout
- 修改了我找到here左对齐集合视图的代码。
它看起来像这样:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count];
CGFloat leftMargin = self.sectionInset.left;
NSMutableArray *lines = [NSMutableArray array];
NSMutableArray *currLine = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) {
// Handle new line
BOOL newLine = attributes.frame.origin.x <= leftMargin;
if (newLine) {
leftMargin = self.sectionInset.left; //will add outside loop
currLine = [NSMutableArray arrayWithObject:attributes];
} else {
[currLine addObject:attributes];
}
if ([lines indexOfObject:currLine] == NSNotFound) {
[lines addObject:currLine];
}
// Align to the left
CGRect newLeftAlignedFrame = attributes.frame;
newLeftAlignedFrame.origin.x = leftMargin;
attributes.frame = newLeftAlignedFrame;
leftMargin += attributes.frame.size.width + self.minimumInteritemSpacing;
[newAttributesForElementsInRect addObject:attributes];
}
// Center left aligned lines
for (NSArray *line in lines) {
UICollectionViewLayoutAttributes *lastAttributes = line.lastObject;
CGFloat space = CGRectGetWidth(self.collectionView.frame) - CGRectGetMaxX(lastAttributes.frame);
for (UICollectionViewLayoutAttributes *attributes in line) {
CGRect newFrame = attributes.frame;
newFrame.origin.x = newFrame.origin.x + space / 2;
attributes.frame = newFrame;
}
}
return newAttributesForElementsInRect;
}
希望它可以帮助某人:)
答案 6 :(得分:1)
只需链接此流程布局即可。您也可以将中心,左侧,右侧对齐。
//
// CellAllignmentFlowLayout.swift
// UICollectionView
//
// Created by rajeshkumar Lingavel on 8/11/15.
// Copyright © 2015 rajeshkumar Lingavel. All rights reserved.
//
import UIKit
enum SZAlignment:Int {
case Center,
left,
Right
}
class CellAllignmentFlowLayout: UICollectionViewFlowLayout {
var alignment:SZAlignment!
var padding:CGFloat!
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// NSArray *allAttributesInRect = [super
// layoutAttributesForElementsInRect:rect];
let allAttributesInRect:NSArray = super.layoutAttributesForElementsInRect(rect)!
var changedAttributes:NSArray = NSArray()
switch(alignment.rawValue){
case 0:
changedAttributes = alignCenter(allAttributesInRect)
case 1:
changedAttributes = alignLeft(allAttributesInRect)
case 2:
changedAttributes = alignRight(allAttributesInRect)
default:
assertionFailure("No Direction")
}
return changedAttributes as? [UICollectionViewLayoutAttributes]
}
private func alignCenter(allAttributesInRect:NSArray) -> NSArray{
let numberOfSection:Int = (self.collectionView?.numberOfSections())!
let redefiendArray = NSMutableArray()
for i in 0 ..< numberOfSection {
let thisSectionObjects = sectionObjects(allAttributesInRect, section: i)
let totalLines = numberOfLines(thisSectionObjects, section: i)
let lastrowObjects = lastRow(thisSectionObjects, numberOfRows: totalLines, section: i)
let lastRowObjectsRow = setMiddleTheLastRow(lastrowObjects)
let start = (thisSectionObjects.count - lastrowObjects.count)
for j in start..<thisSectionObjects.count{
thisSectionObjects.replaceObjectAtIndex(j, withObject: lastRowObjectsRow.objectAtIndex(j - start))
}
redefiendArray.addObjectsFromArray(thisSectionObjects as [AnyObject])
}
return redefiendArray
}
private func alignLeft(allAttributesInRect:NSArray) -> NSArray{
return allAttributesInRect;
}
private func alignRight(allAttributesInRect:NSArray) -> NSArray{
return allAttributesInRect;
}
private func getTotalLenthOftheSection(section:Int,allAttributesInRect:NSArray) -> CGFloat{
var totalLength:CGFloat = 0.0
totalLength = totalLength + (CGFloat (((self.collectionView?.numberOfItemsInSection(section))! - 1)) * padding)
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
totalLength = totalLength + attributes.frame.width
}
}
return totalLength
}
private func numberOfLines(allAttributesInRect:NSArray,section:Int)-> Int{
var totalLines:Int = 0
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
if (attributes.frame.origin.x == self.sectionInset.left){
totalLines = totalLines + 1
}
}
}
return totalLines
}
private func sectionObjects(allAttributesInRect:NSArray,section:Int) -> NSMutableArray{
let objects:NSMutableArray = NSMutableArray()
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
objects.addObject(attributes)
}
}
return objects
}
private func lastRow(allAttributesInRect:NSArray,numberOfRows:Int,section:Int) -> NSMutableArray{
var totalLines:Int = 0
let lastRowArrays:NSMutableArray = NSMutableArray()
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
if (attributes.frame.origin.x == self.sectionInset.left){
totalLines = totalLines + 1
if(totalLines == numberOfRows){
lastRowArrays.addObject(attributes)
}
}
else{
if(totalLines == numberOfRows){
lastRowArrays.addObject(attributes)
}
}
}
}
return lastRowArrays
}
private func setMiddleTheLastRow(lastRowAttrs:NSMutableArray)->NSMutableArray{
let redefinedValues = NSMutableArray()
let totalLengthOftheView = self.collectionView?.frame.width
var totalLenthOftheCells:CGFloat = 0.0
totalLenthOftheCells = totalLenthOftheCells + (CGFloat (lastRowAttrs.count) - 1) * padding
for attrs in lastRowAttrs{
totalLenthOftheCells = totalLenthOftheCells + attrs.frame.width
}
var initalValue = (totalLengthOftheView!/2) - (totalLenthOftheCells/2)
for i in 0..<lastRowAttrs.count {
let changeingAttribute:UICollectionViewLayoutAttributes = lastRowAttrs[i] as! UICollectionViewLayoutAttributes
var frame = changeingAttribute.frame
frame.origin.x = initalValue
changeingAttribute.frame = frame
redefinedValues.addObject(changeingAttribute)
initalValue = initalValue + changeingAttribute.frame.width + padding
}
return redefinedValues;
}
}
答案 7 :(得分:1)
Swift 3.0 版本的类:
class UICollectionViewFlowCenterLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let suggestedAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
guard scrollDirection == .vertical else { return suggestedAttributes }
var newAttributes: [UICollectionViewLayoutAttributes] = []
var currentRowAttributes: [UICollectionViewLayoutAttributes] = []
var yOffset:CGFloat = sectionInset.top
for attributes in suggestedAttributes {
if attributes.frame.origin.y != yOffset {
centerSingleRowWithItemsAttributes(attributes: ¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
currentRowAttributes = []
yOffset = attributes.frame.origin.y
}
currentRowAttributes += [attributes]
}
centerSingleRowWithItemsAttributes(attributes: ¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
return newAttributes
}
private func centerSingleRowWithItemsAttributes( attributes: inout [UICollectionViewLayoutAttributes], rect: CGRect) {
guard let item = attributes.last else { return }
let itemsCount = CGFloat(attributes.count)
let sideInsets = rect.width - (item.frame.width * itemsCount) - (minimumInteritemSpacing * (itemsCount - 1))
var leftOffset = sideInsets / 2
for attribute in attributes {
attribute.frame.origin.x = leftOffset
leftOffset += attribute.frame.width + minimumInteritemSpacing
}
}
}
答案 8 :(得分:0)
我制作了 Swift 4 版本的Kyle Truscott answer:
import UIKit
class CenterFlowLayout: UICollectionViewFlowLayout {
private var attrCache = [IndexPath: UICollectionViewLayoutAttributes]()
override func prepare() {
attrCache = [:]
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var updatedAttributes = [UICollectionViewLayoutAttributes]()
let sections = self.collectionView?.numberOfSections ?? 0
var section = 0
while section < sections {
let items = self.collectionView?.numberOfItems(inSection: section) ?? 0
var item = 0
while item < items {
let indexPath = IndexPath(row: item, section: section)
if let attributes = layoutAttributesForItem(at: indexPath), attributes.frame.intersects(rect) {
updatedAttributes.append(attributes)
}
let headerKind = UICollectionElementKindSectionHeader
if let headerAttributes = layoutAttributesForSupplementaryView(ofKind: headerKind, at: indexPath) {
updatedAttributes.append(headerAttributes)
}
let footerKind = UICollectionElementKindSectionFooter
if let footerAttributes = layoutAttributesForSupplementaryView(ofKind: footerKind, at: indexPath) {
updatedAttributes.append(footerAttributes)
}
item += 1
}
section += 1
}
return updatedAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if let attributes = attrCache[indexPath] {
return attributes
}
// Find the other items in the same "row"
var rowBuddies = [UICollectionViewLayoutAttributes]()
// Calculate the available width to center stuff within
// sectionInset is NOT applicable here because a) we're centering stuff
// and b) Flow layout has arranged the cells to respect the inset. We're
// just hijacking the X position.
var collectionViewWidth: CGFloat = 0
if let collectionView = collectionView {
collectionViewWidth = collectionView.bounds.width - collectionView.contentInset.left
- collectionView.contentInset.right
}
// To find other items in the "row", we need a rect to check intersects against.
// Take the item attributes frame (from vanilla flow layout), and stretch it out
var rowTestFrame: CGRect = super.layoutAttributesForItem(at: indexPath)?.frame ?? .zero
rowTestFrame.origin.x = 0
rowTestFrame.size.width = collectionViewWidth
let totalRows = self.collectionView?.numberOfItems(inSection: indexPath.section) ?? 0
// From this item, work backwards to find the first item in the row
// Decrement the row index until a) we get to 0, b) we reach a previous row
var rowStartIDX = indexPath.row
while true {
let prevIDX = rowStartIDX - 1
if prevIDX < 0 {
break
}
let prevPath = IndexPath(row: prevIDX, section: indexPath.section)
let prevFrame: CGRect = super.layoutAttributesForItem(at: prevPath)?.frame ?? .zero
// If the item intersects the test frame, it's in the same row
if prevFrame.intersects(rowTestFrame) {
rowStartIDX = prevIDX
} else {
// Found previous row, escape!
break
}
}
// Now, work back UP to find the last item in the row
// For each item in the row, add it's attributes to rowBuddies
var buddyIDX = rowStartIDX
while true {
if buddyIDX > totalRows - 1 {
break
}
let buddyPath = IndexPath(row: buddyIDX, section: indexPath.section)
if let buddyAttributes = super.layoutAttributesForItem(at: buddyPath),
buddyAttributes.frame.intersects(rowTestFrame),
let buddyAttributesCopy = buddyAttributes.copy() as? UICollectionViewLayoutAttributes {
// If the item intersects the test frame, it's in the same row
rowBuddies.append(buddyAttributesCopy)
buddyIDX += 1
} else {
// Encountered next row
break
}
}
let flowDelegate = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout
let selector = #selector(UICollectionViewDelegateFlowLayout.collectionView(_:layout:minimumInteritemSpacingForSectionAt:))
let delegateSupportsInteritemSpacing = flowDelegate?.responds(to: selector) ?? false
// x-x-x-x ... sum up the interim space
var interitemSpacing = minimumInteritemSpacing
// Check for minimumInteritemSpacingForSectionAtIndex support
if let collectionView = collectionView, delegateSupportsInteritemSpacing && rowBuddies.count > 0 {
interitemSpacing = flowDelegate?.collectionView?(collectionView,
layout: self,
minimumInteritemSpacingForSectionAt: indexPath.section) ?? 0
}
let aggregateInteritemSpacing = interitemSpacing * CGFloat(rowBuddies.count - 1)
// Sum the width of all elements in the row
var aggregateItemWidths: CGFloat = 0
for itemAttributes in rowBuddies {
aggregateItemWidths += itemAttributes.frame.width
}
// Build an alignment rect
// | |x-x-x-x| |
let alignmentWidth = aggregateItemWidths + aggregateInteritemSpacing
let alignmentXOffset: CGFloat = (collectionViewWidth - alignmentWidth) / 2
// Adjust each item's position to be centered
var previousFrame: CGRect = .zero
for itemAttributes in rowBuddies {
var itemFrame = itemAttributes.frame
if previousFrame.equalTo(.zero) {
itemFrame.origin.x = alignmentXOffset
} else {
itemFrame.origin.x = previousFrame.maxX + interitemSpacing
}
itemAttributes.frame = itemFrame
previousFrame = itemFrame
// Finally, add it to the cache
attrCache[itemAttributes.indexPath] = itemAttributes
}
return attrCache[indexPath]
}
}