ランダムなセルを持つ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 ]
最初に背景を少し説明します。UICollectionView
は、セルがビューに配置される方法を決定するUICollectionViewLayout
と組み合わされます。これは、コレクションビューが非常に柔軟であることを意味します(これを使用してほぼすべてのレイアウトを作成できます)が、レイアウトの変更は少し混乱する可能性があります。
まったく新しいレイアウトクラスの作成は複雑であるため、代わりにデフォルトのレイアウト(UICollectionViewFlowLayout
)を変更して、中央揃えにすることをお勧めします。さらに簡単にするために、フローレイアウト自体のサブクラス化を避けたいと思うでしょう。
これが1つのアプローチです(最良のアプローチではないかもしれませんが、私が考えることができる最初のアプローチです)-次のように、セルを2つのセクションに分割します。
[ x x x x x ] <-- Section 1
[ x x x x x ] <-- Section 1
[ x x ] <-- Section 2
スクロールビューの幅と各行に収まるセルの数がわかっていれば、これはかなり簡単です。
次に、collectionView:layout:insetForSectionAtIndex:
デリゲートメソッドは、2番目のセクションが縦方向に中央に表示されるように、マージンを設定します。これが完了したら、適切なセクション分割/インセットを再計算して、縦向きと横向きの両方をサポートできるようにする必要があります。
ここにやや似たような質問があります- ICollectionViewのセルを中央に揃える方法 .
このようなことをしなければなりませんでしたが、1つのセクションにすべてのセルが必要でした。 UICollectionViewFlowLayout
を中央のセルに拡張するのは非常に簡単でした。ポッドを作成しました:
Swift 4.1およびiOS 11、必要に応じて、2つの完全な実装のいずれかを選択して、問題を修正できます。
UICollectionViewCell
sを中央に配置以下の実装は、UICollectionViewLayout
の- layoutAttributesForElements(in:)
およびUICollectionViewFlowLayout
の- itemSize
を使用して、 UICollectionView
のセルを中央に配置します:
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")
}
}
_
期待される結果:
UICollectionViewCell
s以下の実装は、UICollectionViewLayout
の- layoutAttributesForElements(in:)
、UICollectionViewFlowLayout
の- estimatedItemSize
およびUILabel
の- preferredMaxLayoutWidth
は、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")
}
}
_
期待される結果:
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;
}
これは、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
}
}
}
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;
}
それが誰かを助けることを願っています:)
Swift 3.クラスのバージョン:
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
}
}
}
このフローレイアウトをリンクするだけです。中央揃え、左揃え、右揃えもできます。
//
// 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;
}
}
カイル・トラスコットのSwift 4バージョンを作成しました 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]
}
}