如何在UICollectionView中居中行?

时间:2013-05-14 13:12:56

标签: ios objective-c uicollectionview

我有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    ]  

9 个答案:

答案 0 :(得分:44)

我必须做这样的事情,但需要一部分中的所有细胞。将UICollectionViewFlowLayout扩展到中心单元格非常简单。我做了一个吊舱:

https://github.com/keighl/KTCenterFlowLayout

enter image description here

答案 1 :(得分:23)

首先是一些背景 - UICollectionViewUICollectionViewLayout结合,确定单元格在视图中的放置方式。这意味着集合视图非常灵活(您可以使用它创建几乎任何布局),但也意味着修改布局可能会有点混乱。

创建一个全新的布局类很复杂,所以你想尝试修改默认布局(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以下完整实施之一来解决您的问题。

#1。中心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:)

#2。中心自动调整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")
    }

}

预期结果:

itemSize

答案 3 :(得分:2)

如果任何人有一个具有2列的CollectionView,并且项目数是奇数,则最后一项应该居中对齐。然后使用此

DNLastItemCenteredLayout

- (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(&currentRowAttributes, 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(&currentRowAttributes, 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左对齐集合视图的代码。

  1. 左对齐集合视图
  2. 对行数组的属性进行分组
  3. 每行:
    • 计算右侧的空间
    • 为行
    • 的每个属性添加一半空格
  4. 它看起来像这样:

    - (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: &currentRowAttributes, rect: rect)
                newAttributes += currentRowAttributes
                currentRowAttributes = []
                yOffset = attributes.frame.origin.y
            }
            currentRowAttributes += [attributes]
        }
        centerSingleRowWithItemsAttributes(attributes: &currentRowAttributes, 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]
    }
}