在UICollectionView中左对齐单元格

Dam*_*ien 78 objective-c ios uicollectionview uicollectionviewlayout

我在我的项目中使用UICollectionView,其中一行中有多个不同宽度的单元格.根据:https: //developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html

它使用相同的填充将细胞扩散到整个线上.这种情况按预期发生,除了我想左对齐它们,并硬编码填充宽度.

我想我需要继承UICollectionViewFlowLayout,但是在线阅读了一些教程等之后,我似乎并不知道这是如何工作的.

Ang*_*qui 128

当线仅由1个项组成或者过于复杂时,此处的其他解决方案无法正常工作.

基于Ryan给出的示例,我通过检查新元素的Y位置来更改代码以检测新行.非常简单快速的性能.

迅速:

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        var leftMargin = sectionInset.left
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.origin.y >= maxY {
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }

        return attributes
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您希望补充视图保持其大小,请在forEach调用中的闭包顶部添加以下内容:

guard layoutAttribute.representedElementCategory == .cell else {
    return
}
Run Code Online (Sandbox Code Playgroud)

Objective-C的:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.
    CGFloat maxY = -1.0f;

    //this loop assumes attributes are in IndexPath order
    for (UICollectionViewLayoutAttributes *attribute in attributes) {
        if (attribute.frame.origin.y >= maxY) {
            leftMargin = self.sectionInset.left;
        }

        attribute.frame = CGRectMake(leftMargin, attribute.frame.origin.y, attribute.frame.size.width, attribute.frame.size.height);

        leftMargin += attribute.frame.size.width + self.minimumInteritemSpacing;
        maxY = MAX(CGRectGetMaxY(attribute.frame), maxY);
    }

    return attributes;
}
Run Code Online (Sandbox Code Playgroud)

  • 对于使用,请执行此 self.collectionView.collectionViewLayout = LeftAlignedCollectionViewFlowLayout() (4认同)
  • 谢谢!我收到了一个警告,通过复制数组中的属性来修复`let attributes = super.layoutAttributesForElementsInRect(rect)?. map {$ 0.copy()as!UICollectionViewLayoutAttributes}` (3认同)
  • 对于那些可能偶然发现这个寻找中心对齐版本的人,我在这里发布了Angel的答案的修改版本:http://stackoverflow.com/a/38254368/2845237 (2认同)

Mis*_*cha 45

这个问题的答案中包含许多好主意.但是,大多数都有一些缺点:

  • 不检查单元格y值的解决方案仅适用于单行布局.它们无法用于具有多行的集合视图布局.
  • AngelGarcíaOlloqui的答案那样检查y值的解决方案只有在所有单元格具有相同高度时才有效.对于高度可变的细胞,它们会失败.
  • 大多数解决方案仅覆盖该layoutAttributesForElements(in rect: CGRect)功能.他们不会覆盖layoutAttributesForItem(at indexPath: IndexPath).这是一个问题,因为集合视图定期调用后一个函数来检索特定索引路径的布局属性.如果您没有从该函数返回适当的属性,则可能会遇到所有类型的视觉错误,例如在插入和删除单元格动画期间或通过设置集合视图布局使用自定义单元格时estimatedItemSize.在苹果文档的状态:

    每个自定义布局对象都应该实现该layoutAttributesForItemAtIndexPath:方法.

  • 许多解决方案也会对rect传递给layoutAttributesForElements(in rect: CGRect)函数的参数进行假设.例如,许多都是基于这样的假设:rect始终在新行的开头处开始,而不一定是这种情况.

换句话说:

此页面上建议的大多数解决方案适用于某些特定应用程序,但它们在每种情况下都无法正常工作.


AlignedCollectionViewFlowLayout

为了解决这些问题,我创建了一个UICollectionViewFlowLayout子类,它遵循了mattChris Wagner在回答类似问题时提出的类似想法.它可以对齐细胞

⬅︎ :

左对齐布局

或者➡︎ :

右对齐布局

并且还提供垂直对齐各自行中的单元格的选项(如果它们的高度不同).

你可以在这里下载它:

https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout

用法很简单,并在README文件中进行了解释.您基本上创建了一个实例AlignedCollectionViewFlowLayout,指定所需的对齐方式并将其分配给集合视图的collectionViewLayout属性:

 let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, 
                                                         verticalAlignment: .top)

 yourCollectionView.collectionViewLayout = alignedFlowLayout
Run Code Online (Sandbox Code Playgroud)

(它也可以在Cocoapods找到.)


它是如何工作的(对于左对齐的单元格):

这里的概念是完全依赖layoutAttributesForItem(at indexPath: IndexPath)功能.在layoutAttributesForElements(in rect: CGRect)我们只需获取其中所有单元格的索引路径,rect然后为每个索引路径调用第一个函数以检索正确的帧:

override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

    // We may not change the original layout attributes 
    // or UICollectionViewFlowLayout might complain.
    let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))

    layoutAttributesObjects?.forEach({ (layoutAttributes) in
        if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
            let indexPath = layoutAttributes.indexPath
            // Retrieve the correct frame from layoutAttributesForItem(at: indexPath):
            if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
                layoutAttributes.frame = newFrame
            }
        }
    })

    return layoutAttributesObjects
}
Run Code Online (Sandbox Code Playgroud)

(该copy()函数只是创建数组中所有布局属性的深层副本.您可以查看其实现的源代码.)

所以现在我们唯一要做的就是layoutAttributesForItem(at indexPath: IndexPath)正确实现这个功能.超类UICollectionViewFlowLayout已经在每一行中放置了正确数量的单元格,因此我们只需将它们在各自的行内移位.困难在于计算将每个单元格向左移动所需的空间量.

由于我们希望在单元格之间具有固定的间距,因此核心思想是假设前一个单元格(当前布局的单元格左侧的单元格)已经正确定位.然后我们只需要将单元格间距添加到maxX前一个单元格框架的origin.x值,这就是当前单元格框架的值.

现在我们只需要知道我们何时到达一行的开头,这样我们就不会在前一行的单元格旁边对齐一个单元格.(这不仅会导致布局不正确,而且还会非常滞后.)因此我们需要一个递归锚点.我用来查找递归锚点的方法如下:

要确定索引i处的单元格是否与索引为i-1的单元格位于同一行...

 +---------+----------------------------------------------------------------+---------+
 |         |                                                                |         |
 |         |     +------------+                                             |         |
 |         |     |            |                                             |         |
 | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
 |  inset  |     |intersection|        |                     |   line rect  |  inset  |
 |         |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -|         |
 | (left)  |     |            |             current item                    | (right) |
 |         |     +------------+                                             |         |
 |         |     previous item                                              |         |
 +---------+----------------------------------------------------------------+---------+
Run Code Online (Sandbox Code Playgroud)

...我在当前单元格周围"绘制"一个矩形,并将其拉伸到整个集合视图的宽度上.作为UICollectionViewFlowLayout中心垂直所有单元格,同一行中的每个单元格必须与此矩形相交.

因此,我只是检查具有索引i-1的单元格是否与从具有索引i的单元格创建的该行矩形相交.

  • 如果它确实相交,则索引为i的单元格不是该行中最左边的单元格.
    →获取前一个单元格的框架(索引为i-1)并移动旁边的当前单元格.

  • 如果它不相交,则索引为i的单元格是该行中最左边的单元格.
    →将单元格移动到集合视图的左边缘(不更改其垂直位置).

我不会在layoutAttributesForItem(at indexPath: IndexPath)这里发布函数的实际实现,因为我认为最重要的部分是理解这个想法,你总是可以在源代码中检查我的实现.(这比这里解释的要复杂一点,因为我还允许.right对齐和各种垂直对齐选项.但是,它遵循相同的想法.)


哇,我想这是我在Stackoverflow上写过的最长的答案.我希望这有帮助.


Fat*_*tie 33

2019年的简单解决方案

这是多年来发生了很大变化的令人沮丧的问题之一。现在很容易。

基本上你只需这样做:

    // as you move across one row ...
    a.frame.origin.x = x
    x += a.frame.width + minimumInteritemSpacing
    // and, obviously start fresh again each row
Run Code Online (Sandbox Code Playgroud)

您现在需要的只是样板代码:

override func layoutAttributesForElements(
                  in rect: CGRect)->[UICollectionViewLayoutAttributes]? {
    
    guard let att = super.layoutAttributesForElements(in: rect) else { return [] }
    var x: CGFloat = sectionInset.left
    var y: CGFloat = -1.0
    
    for a in att {
        if a.representedElementCategory != .cell { continue }
        
        if a.frame.origin.y >= y { x = sectionInset.left }
        
        a.frame.origin.x = x
        x += a.frame.width + minimumInteritemSpacing
        
        y = a.frame.maxY
    }
    return att
}
Run Code Online (Sandbox Code Playgroud)

只需将其复制并粘贴到 a UICollectionViewFlowLayout- 你就完成了。

复制和粘贴的完整工作解决方案:

这是整件事:

class TagsLayout: UICollectionViewFlowLayout {
    
    required override init() {super.init(); common()}
    required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder); common()}
    
    private func common() {
        estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        minimumLineSpacing = 10
        minimumInteritemSpacing = 10
    }
    
    override func layoutAttributesForElements(
                    in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        guard let att = super.layoutAttributesForElements(in:rect) else {return []}
        var x: CGFloat = sectionInset.left
        var y: CGFloat = -1.0
        
        for a in att {
            if a.representedElementCategory != .cell { continue }
            
            if a.frame.origin.y >= y { x = sectionInset.left }
            a.frame.origin.x = x
            x += a.frame.width + minimumInteritemSpacing
            y = a.frame.maxY
        }
        return att
    }
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

最后...

感谢上面首先澄清这一点的@AlexShubin!


Ima*_*tit 24

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


#1.左对齐自动调整UICollectionViewCells

下面的实现显示了如何使用UICollectionViewLayout's layoutAttributesForElements(in:),UICollectionViewFlowLayout's estimatedItemSizeUILabel's 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()

        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)
    }

}
Run Code Online (Sandbox Code Playgroud)

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 }) {
            // Set the initial left inset
            var leftInset = 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
    }

}
Run Code Online (Sandbox Code Playgroud)

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")
    }

}
Run Code Online (Sandbox Code Playgroud)

预期结果:

在此输入图像描述


#2.左对齐UICollectionViewCells固定大小

下面的实现显示了如何使用UICollectionViewLayout's layoutAttributesForElements(in:)UICollectionViewFlowLayout's 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()

        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)
    }

}
Run Code Online (Sandbox Code Playgroud)

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 }) {
            // Set the initial left inset
            var leftInset = 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
    }

}
Run Code Online (Sandbox Code Playgroud)

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")
    }

}
Run Code Online (Sandbox Code Playgroud)

预期结果:

在此输入图像描述


Mik*_*and 21

问题已经持续了一段时间,但没有答案,这是一个很好的问题.答案是覆盖UICollectionViewFlowLayout子类中的一个方法:

@implementation MYFlowLayoutSubclass

//Note, the layout's minimumInteritemSpacing (default 10.0) should not be less than this. 
#define ITEM_SPACING 10.0f

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.

    //this loop assumes attributes are in IndexPath order
    for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) {
        if (attributes.frame.origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left; //will add outside loop
        } else {
            CGRect newLeftAlignedFrame = attributes.frame;
            newLeftAlignedFrame.origin.x = leftMargin;
            attributes.frame = newLeftAlignedFrame;
        }

        leftMargin += attributes.frame.size.width + ITEM_SPACING;
        [newAttributesForElementsInRect addObject:attributes];
    }   

    return newAttributesForElementsInRect;
}

@end
Run Code Online (Sandbox Code Playgroud)

按照Apple的建议,您可以从super获取布局属性并对其进行迭代.如果它是行中的第一个(由其origin.x定义在左边距上),则不管它并将x重置为零.然后对于第一个单元格和每个单元格,添加该单元格的宽度加上一些边距.这将传递给循环中的下一个项目.如果它不是第一个项目,则将其origin.x设置为正在运行的计算边距,并将新元素添加到数组中.

  • 谢谢......我最终使用了UICollectionViewLeftAlignedLayout:https://github.com/mokagio/UICollectionViewLeftAlignedLayout.它覆盖了更多方法. (3认同)

mkl*_*klb 9

我有同样的问题,试试Cocoapod UICollectionViewLeftAlignedLayout.只需将它包含在您的项目中并按如下方式初始化:

UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init];
UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
Run Code Online (Sandbox Code Playgroud)


MkV*_*Val 9

如果您的最低部署目标是iOS的13,我强烈建议你把优势成分布局(DOC这里,WWDC演讲在这里)。

我最初确实在这里尝试了一些最佳答案。不幸的是,我们遇到了一个问题,其中一些单元格往往会间歇性地消失。对我们来说,这是在调用 UICollectionView 的reloadData函数之后发生的。同样重要的是要注意我们的单元格具有可变宽度,也就是自动调整大小。

让我给你看一个例子。假设我们需要显示一个包含关键字气泡列表的页面。

在此处输入图片说明

以下是使用组合布局实现这一目标可能需要的内容。

override func viewDidLoad() {
  super.viewDidLoad()
  ...
  collectionView.collectionViewLayout = createLeftAlignedLayout()
}

private func createLeftAlignedLayout() -> UICollectionViewLayout {
  let item = NSCollectionLayoutItem(          // this is your cell
    layoutSize: NSCollectionLayoutSize(
      widthDimension: .estimated(40),         // variable width
      heightDimension: .absolute(48)          // fixed height
    )
  )
  
  let group = NSCollectionLayoutGroup.horizontal(
    layoutSize: .init(
      widthDimension: .fractionalWidth(1.0),  // 100% width as inset by its Section
      heightDimension: .estimated(50)         // variable height; allows for multiple rows of items
    ),
    subitems: [item]
  )
  group.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
  group.interItemSpacing = .fixed(10)         // horizontal spacing between cells
  
  return UICollectionViewCompositionalLayout(section: .init(group: group))
}
Run Code Online (Sandbox Code Playgroud)

如您所见,这非常简单。


cse*_*_17 6

此页面上的大多数解决方案都太复杂了。左对齐它们的最简单方法,即使只有 1 个单元格,也是返回以下边缘插入:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

    if collectionView.numberOfItems(inSection: section) == 1 {
        let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
        return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: collectionView.frame.width - flowLayout.itemSize.width)
    } else {
        return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }
}
Run Code Online (Sandbox Code Playgroud)


Eva*_*n R 5

Michael Sand的回答的基础上,我创建了一个子UICollectionViewFlowLayout类库来做左,右或完全(基本上是默认的)水平对齐 - 它还允许您设置每个单元格之间的绝对距离.我也计划为它添加水平中心对齐和垂直对齐.

https://github.com/eroth/ERJustifiedFlowLayout


Gre*_*egP 5

在迅速.根据迈克尔斯的回答

override func layoutAttributesForElementsInRect(rect: CGRect) ->     [UICollectionViewLayoutAttributes]? {
    guard let oldAttributes = super.layoutAttributesForElementsInRect(rect) else {
        return super.layoutAttributesForElementsInRect(rect)
    }
    let spacing = CGFloat(50) // REPLACE WITH WHAT SPACING YOU NEED
    var newAttributes = [UICollectionViewLayoutAttributes]()
    var leftMargin = self.sectionInset.left
    for attributes in oldAttributes {
        if (attributes.frame.origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left
        } else {
            var newLeftAlignedFrame = attributes.frame
            newLeftAlignedFrame.origin.x = leftMargin
            attributes.frame = newLeftAlignedFrame
        }

        leftMargin += attributes.frame.width + spacing
        newAttributes.append(attributes)
    }
    return newAttributes
}
Run Code Online (Sandbox Code Playgroud)


Rya*_*los 5

这是Swift中的原始答案。它仍然在大多数情况下运行良好。

class LeftAlignedFlowLayout: UICollectionViewFlowLayout {

    private override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElementsInRect(rect)

        var leftMargin = sectionInset.left

        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.origin.x == sectionInset.left {
                leftMargin = sectionInset.left
            }
            else {
                layoutAttribute.frame.origin.x = leftMargin
            }

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
        }

        return attributes
    }
}
Run Code Online (Sandbox Code Playgroud)

例外:自动调整单元格

可悲的是有一个大例外。如果您使用UICollectionViewFlowLayoutestimatedItemSize。内部UICollectionViewFlowLayout正在改变事情。我还没有完全找到它,但是layoutAttributesForElementsInRect在自动调整单元格大小之后,它清除了调用其他方法的方法。从我的反复试验中,我发现layoutAttributesForItemAtIndexPath在自动调整大小期间似乎需要分别调用每个单元。此更新LeftAlignedFlowLayout适用于estimatedItemSize。它也适用于静态大小的单元格,但是额外的布局调用使我在不需要自动调整单元格大小的任何时候都可以使用原始答案。

class LeftAlignedFlowLayout: UICollectionViewFlowLayout {

    private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let layoutAttribute = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes

        // First in a row.
        if layoutAttribute?.frame.origin.x == sectionInset.left {
            return layoutAttribute
        }

        // We need to align it to the previous item.
        let previousIndexPath = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section)
        guard let previousLayoutAttribute = self.layoutAttributesForItemAtIndexPath(previousIndexPath) else {
            return layoutAttribute
        }

        layoutAttribute?.frame.origin.x = previousLayoutAttribute.frame.maxX + self.minimumInteritemSpacing

        return layoutAttribute
    }
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*bin 5

基于此处的答案,但修复了当您的集合视图也有页眉或页脚时的崩溃和对齐问题。只对齐左单元格:

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)
        
        var leftMargin = sectionInset.left
        var prevMaxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            
            guard layoutAttribute.representedElementCategory == .cell else {
                return
            }
            
            if layoutAttribute.frame.origin.y >= prevMaxY {
                leftMargin = sectionInset.left
            }
            
            layoutAttribute.frame.origin.x = leftMargin
            
            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            prevMaxY = layoutAttribute.frame.maxY
        }
        
        return attributes
    }
}
Run Code Online (Sandbox Code Playgroud)


nik*_*iku 5

如果你们中的任何人遇到问题 -集合视图右侧的某些单元格超出了集合视图的边界。那么请使用这个 -

class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        var leftMargin : CGFloat = sectionInset.left
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            if Int(layoutAttribute.frame.origin.y) >= Int(maxY) {
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }
        return attributes
    }
}
Run Code Online (Sandbox Code Playgroud)

使用INT代替比较CGFloat值。


小智 5

基于所有答案。适用于 leftToRight 和 rightToLeft

class AlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
    {
        let attributes = super.layoutAttributesForElements(in: rect)

        let ltr = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight
        var leftMargin = ltr ? sectionInset.left : (rect.maxX - sectionInset.right)
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.origin.y >= maxY
            {
                leftMargin = ltr ? sectionInset.left : (rect.maxX - sectionInset.right)
            }

            layoutAttribute.frame.origin.x = leftMargin - (ltr ? 0 : layoutAttribute.frame.width)

            if (ltr)
            {
                leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            }
            else
            {
                leftMargin -= layoutAttribute.frame.width + minimumInteritemSpacing
            }
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }

        return attributes
    }
}
Run Code Online (Sandbox Code Playgroud)