如何在 UICollectionView 中居中行?

Posted

技术标签:

【中文标题】如何在 UICollectionView 中居中行?【英文标题】:How can I center rows in UICollectionView? 【发布时间】:2013-05-14 13:12:56 【问题描述】:

我有一个带有随机单元格的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    ]  

【问题讨论】:

【参考方案1】:

我不得不做这样的事情,但需要一个部分中的所有单元格。将UICollectionViewFlowLayout 扩展到中心单元非常简单。我做了一个豆荚:

https://github.com/keighl/KTCenterFlowLayout

【讨论】:

要让 KTCenterFlowLayout 工作,我必须在代码中明确设置单元格大小,即使它已经通过情节提要设置:let layout = KTCenterFlowLayout(); layout.itemSize = CGSizeMake(85, 85) 您也可以在界面生成器中将其设置为集合视图的布局。谢谢凯尔,这太棒了! 非常感谢,凯尔!为我节省了几个小时! 我的收藏视图想要完全相同的东西。如何使用 Objective-C 使用此布局? 我制作了它的 Swift 4 版本:***.com/a/47815971/4386668。谢谢@Kyle Truscott!【参考方案2】:

先介绍一下背景 - 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? - 详细介绍了插入方法,尽管它并没有完全尝试做和你一样的事情。

【讨论】:

好吧,我不知道这是不是最好的方法,但它确实有效。这就是我所需要的。谢谢。 我认为它可能是最简单的,就行数/不子类化布局而言。如果您最终要做更多的布局工作,您可能需要考虑这种方法。 你也可以继承UICollectionViewFlowLayout而不是UICollectionViewLayout。布局仍然是基于流的,只是进行了微小的更改,这样做可以为您完成大部分繁重的工作,您只需自定义所需的部分即可。 如果我有不同宽度的单元格,这种方法似乎使它更加复杂。您对我如何使用具有不同宽度的单元格来实现这一点有任何建议吗?谢谢【参考方案3】:

在 Swift 4.1 和 ios 11 中,根据您的需要,您可以选择以下两个完整实现中的一个来解决您的问题。


#1。以固定大小居中UICollectionViewCells

下面的实现展示了如何使用UICollectionViewLayoutlayoutAttributesForElements(in:)UICollectionViewFlowLayoutitemSize 来使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")
    


预期结果:


#2。中心自动调整大小UICollectionViewCells

下面的实现展示了如何使用UICollectionViewLayoutlayoutAttributesForElements(in:)UICollectionViewFlowLayoutestimatedItemSizeUILabelpreferredMaxLayoutWidth来使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")
    


预期结果:


【讨论】:

这是这里最全面的答案,应该点赞。【参考方案4】:

如果有人有一个包含 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;

【讨论】:

【参考方案5】:

这可以通过从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
        
    

【讨论】:

【参考方案6】:

我已将 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;

希望它可以帮助某人:)

【讨论】:

【参考方案7】:

我制作了 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]
    

【讨论】:

【参考方案8】:

只需链接此流程布局。也可以居中、左、右对齐。

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



【讨论】:

【参考方案9】:

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
        
    

【讨论】:

以上是关于如何在 UICollectionView 中居中行?的主要内容,如果未能解决你的问题,请参考以下文章

在 UICollectionView 中居中第一个 UICollectionViewCell

在 Swift 4 中居中 UICollectionViewCells

如何在ActionBar中居中徽标?

如何在 SwiftUI 中居中裁剪图像

如何在android中行走时计算距离?

如何在 ScrollView 中居中 UIImageView(缩放)