web-dev-qa-db-ja.com

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    ]  
39
deycall

最初に背景を少し説明します。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のセルを中央に揃える方法 .

23
lxt

このようなことをしなければなりませんでしたが、1つのセクションにすべてのセルが必要でした。 UICollectionViewFlowLayoutを中央のセルに拡張するのは非常に簡単でした。ポッドを作成しました:

https://github.com/keighl/KTCenterFlowLayout

enter image description here

45
Kyle Truscott

Swift 4.1およびiOS 11、必要に応じて、2つの完全な実装のいずれかを選択して、問題を修正できます。


#1。固定サイズのUICollectionViewCellsを中央に配置

以下の実装は、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")
    }

}
_

期待される結果:

enter image description here


#2。センターの自動サイズ変更UICollectionViewCells

以下の実装は、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")
    }

}
_

期待される結果:

enter image description here


5
Imanou Petit

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;
}
2
onmyway133

これは、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
        }
    }
}
2

UICollectionViewFlowLayoutをサブクラス化しました-見つかったコードを変更しました here 左揃えのコレクションビュー用。

  1. コレクションビューを左揃え
  2. 行配列の属性をグループ化する
  3. 各行:
    • 右側のスペースを計算します
    • 行の各属性にスペースの半分を追加します

次のようになります。

- (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;
}

それが誰かを助けることを願っています:)

2
chents

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: &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
        }
    }
}
1
danielemm

このフローレイアウトをリンクするだけです。中央揃え、左揃え、右揃えもできます。

 //
    //  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;
    }


}
1
Rajesh Kumar

カイル・トラスコットの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]
    }
}
0
Nominalista