ios 在 UICollectionView 中左对齐单元格

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22539979/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 23:46:20  来源:igfitidea点击:

Left Align Cells in UICollectionView

iosobjective-cuicollectionviewuicollectionviewlayout

提问by Damien

I am using a UICollectionView in my project, where there are multiple cells of differing widths on a line. According to: https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html

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

it spreads the cells out across the line with equal padding. This happens as expected, except I want to left justify them, and hard code a padding width.

它以相等的填充将单元格散布在这条线上。这按预期发生,除了我想左对齐它们,并硬编码填充宽度。

I figure I need to subclass UICollectionViewFlowLayout, however after reading some of the tutorials etc online I just don't seem to get how this works.

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

回答by Angel G. Olloqui

The other solutions in this thread do not work properly, when the line is composed by only 1 item or are over complicated.

当该行仅由 1 个项目组成或过于复杂时,该线程中的其他解决方案无法正常工作。

Based on the example given by Ryan, I changed the code to detect a new line by inspecting the Y position of the new element. Very simple and quick in performance.

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

Swift:

迅速:

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

If you want to have supplementary views keep their size, add the following at the top of the closure in the forEachcall:

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

guard layoutAttribute.representedElementCategory == .cell else {
    return
}

Objective-C:

目标-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;
}

回答by Mischa

There are many great ideas included in the answers to this question. However, most of them have some drawbacks:

这个问题的答案中包含了许多很棒的想法。但是,它们中的大多数都有一些缺点:

  • Solutions that don't check the cell's yvalue only work for single-line layouts. They fail for collection view layouts with multiple lines.
  • Solutions that docheck the yvalue like Angel García Olloqui's answeronly work if all cells have the same height. They fail for cells with a variable height.
  • Most solutions only override the layoutAttributesForElements(in rect: CGRect)function. They do not override layoutAttributesForItem(at indexPath: IndexPath).This is a problem because the collection view periodically calls the latter function to retrieve the layout attributes for a particular index path. If you don't return the proper attributes from that function, you're likely to run into all sort of visual bugs, e.g. during insertion and deletion animations of cells or when using self-sizing cells by setting the collection view layout's estimatedItemSize. The Apple docsstate:

    Every custom layout object is expected to implement the layoutAttributesForItemAtIndexPath:method.

  • Many solutions also make assumptions about the rectparameter that is passed to the layoutAttributesForElements(in rect: CGRect)function. For example, many are based on the assumption that the rectalways starts at the beginning of a new line which is not necessarily the case.

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

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

  • 许多解决方案还对rect传递给layoutAttributesForElements(in rect: CGRect)函数的参数做出假设。例如,许多基于这样的假设,即rect总是从新行的开头开始,但不一定如此。

So in other words:

所以换句话说:

Most of the solutions suggested on this page work for some specific applications, but they don't work as expected in every situation.

此页面上建议的大多数解决方案适用于某些特定应用程序,但它们并非在所有情况下都按预期工作。



AlignedCollectionViewFlowLayout

AlignedCollectionViewFlowLayout

In order to address these issues I've created a UICollectionViewFlowLayoutsubclass that follows a similar idea as suggested by mattand Chris Wagnerin their answers to a similar question. It can either align the cells

为了解决这些问题,我创建了一个UICollectionViewFlowLayout子类,它遵循mattChris Wagner在他们对类似问题的回答中提出的类似想法。它可以对齐单元格

??left:

??

Left-aligned layout

左对齐布局

or ??right:

??

Right-aligned layout

右对齐布局

and additionally offers options to verticallyalign the cells in their respective rows (in case they vary in height).

并另外提供选项以垂直对齐各自行中的单元格(以防它们的高度不同)。

You can simply download it here:

您可以简单地在此处下载:

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

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

The usage is straight-forward and explained in the README file. You basically create an instance of AlignedCollectionViewFlowLayout, specify the desired alignment and assign it to your collection view's collectionViewLayoutproperty:

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

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

 yourCollectionView.collectionViewLayout = alignedFlowLayout

(It's also available on Cocoapods.)

(它也可以在Cocoapods使用。)



How it works (for left-aligned cells):

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

The concept here is to rely solely on the layoutAttributesForItem(at indexPath: IndexPath)function. In the layoutAttributesForElements(in rect: CGRect)we simply get the index paths of all cells within the rectand then call the first function for every index path to retrieve the correct frames:

这里的概念是完全依赖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
}

(The copy()function simply creates a deep copy of all layout attributes in the array. You may look into the source codefor its implementation.)

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

So now the only thing we have to do is to implement the layoutAttributesForItem(at indexPath: IndexPath)function properly. The super class UICollectionViewFlowLayoutalready puts the correct number of cells in each line so we only have to shift them left within their respective row. The difficulty lies in computing the amount of space we need to shift each cell to the left.

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

As we want to have a fixed spacing between the cells the core idea is to just assume that the previous cell (the cell left of the cell that is currently laid out) is already positioned properly. Then we only have to add the cell spacing to the maxXvalue of the previous cell's frame and that's the origin.xvalue for the current cell's frame.

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

Now we only need to know when we've reached the beginning of a line, so that we don't align a cell next to a cell in the previous line. (This would not only result in an incorrect layout but it would also be extremely laggy.) So we need to have a recursion anchor. The approach I use for finding that recursion anchor is the following:

现在我们只需要知道什么时候到达了一行的开头,这样我们就不会在上一行的单元格旁边对齐一个单元格。(这不仅会导致布局不正确,而且会非常滞后。)所以我们需要一个递归锚点。我用于查找递归锚点的方法如下:

To find out if the cell at index iis in the same line as the cell with index i-1...

要找出索引为i的单元格是否与索引为i-1的单元格在同一行...

 +---------+----------------------------------------------------------------+---------+
 |         |                                                                |         |
 |         |     +------------+                                             |         |
 |         |     |            |                                             |         |
 | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
 |  inset  |     |intersection|        |                     |   line rect  |  inset  |
 |         |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -|         |
 | (left)  |     |            |             current item                    | (right) |
 |         |     +------------+                                             |         |
 |         |     previous item                                              |         |
 +---------+----------------------------------------------------------------+---------+

... I "draw" a rectangle around the current cell and stretch it over the width of the whole collection view. As the UICollectionViewFlowLayoutcenters all cells vertically every cell in the same line mustintersect with this rectangle.

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

Thus, I simply check if the cell with index i-1intersects with this line rectangle created from the cell with index i.

因此,我只是检查索引为i-1的单元格是否与从索引为i的单元格创建的线矩形相交。

  • If it does intersect, the cell with index iis not the left most cell in the line.
    → Get the previous cell's frame (with the index i?1) and move the current cell next to it.

  • If it does not intersect, the cell with index iis the left most cell in the line.
    → Move the cell to the left edge of the collection view (without changing its vertical position).

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

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

I won't post the actual implementation of the layoutAttributesForItem(at indexPath: IndexPath)function here because I think the most important part is to understand the ideaand you can always check my implementation in the source code. (It's a little more complicated than explained here because I also allow .rightalignment and various vertical alignment options. However, it follows the same idea.)

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



Wow, I guess this is the longest answer I've ever written on Stackoverflow. I hope this helps.

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

回答by Imanou Petit

With Swift 4.1 and iOS 11, according to your needs, you may choose one of the 2 following complete implementationsin order to fix your problem.

对于 Swift 4.1 和 iOS 11,您可以根据您的需要,选择以下 2 个完整实现之一来解决您的问题。



#1. Left align autoresizing UICollectionViewCells

#1. 左对齐自动尺寸UICollectionViewCell小号

The implementation below shows how to use UICollectionViewLayout's layoutAttributesForElements(in:), UICollectionViewFlowLayout's estimatedItemSizeand UILabel's preferredMaxLayoutWidthin order to left align autoresizing cells in a UICollectionView:

下面的实现显示了如何使用UICollectionViewLayout's layoutAttributesForElements(in:)UICollectionViewFlowLayout'sestimatedItemSizeUILabel'spreferredMaxLayoutWidth来左对齐 a 中的自动调整大小的单元格UICollectionView

CollectionViewController.swift

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

}

FlowLayout.swift

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

}
.copy() as! UICollectionViewLayoutAttributes } guard scrollDirection == .vertical else { return layoutAttributes } // Filter attributes to compute only cell attributes let cellAttributes = layoutAttributes.filter({
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)
    }

}
.representedElementCategory == .cell }) // Group cell attributes by row (cells with same vertical center) and loop on those groups for (_, attributes) in Dictionary(grouping: cellAttributes, by: { (
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 { 
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")
    }

}
.copy() as! UICollectionViewLayoutAttributes } guard scrollDirection == .vertical else { return layoutAttributes } // Filter attributes to compute only cell attributes let cellAttributes = layoutAttributes.filter({
@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
.representedElementCategory == .cell }) // Group cell attributes by row (cells with same vertical center) and loop on those groups for (_, attributes) in Dictionary(grouping: cellAttributes, by: { (
    // as you move across one row ...
    a.frame.origin.x = x
    x += a.frame.width + minimumInteritemSpacing
    // obviously start fresh again each row
.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 } }
.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 } }

CollectionViewCell.swift

CollectionViewCell.swift

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
}

Expected result:

预期结果:

enter image description here

在此处输入图片说明



#2. Left align UICollectionViewCells with fixed size

#2. 左对齐UICollectionViewCells 固定大小

The implementation below shows how to use UICollectionViewLayout's layoutAttributesForElements(in:)and UICollectionViewFlowLayout's itemSizein order to left align cells with predefined size in a UICollectionView:

下面的实现显示了如何使用UICollectionViewLayout'slayoutAttributesForElements(in:)UICollectionViewFlowLayout'sitemSize来左对齐 a 中具有预定义大小的单元格UICollectionView

CollectionViewController.swift

CollectionViewController.swift

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

FlowLayout.swift

FlowLayout.swift

UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init];
UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];

CollectionViewCell.swift

CollectionViewCell.swift

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
}

Expected result:

预期结果:

enter image description here

在此处输入图片说明

回答by Mike Sand

The question has been up a while but there's no answer and it's a good question. The answer is to override one method in the UICollectionViewFlowLayout subclass:

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

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

As recommended by Apple, you get the layout attributes from super and iterate over them. If it's the first in the row (defined by its origin.x being on the left margin), you leave it alone and reset the x to zero. Then for the first cell and every cell, you add the width of that cell plus some margin. This gets passed to the next item in the loop. If it's not the first item, you set it's origin.x to the running calculated margin, and add new elements to the array.

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

回答by Fattie

The simple solution in 2019

2019年的简单解决方案

This is one of those depressing questions where things have changed a lot over the years. It is now easy.

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

Basically you just do this:

基本上你只需这样做:

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

All you need now is the boilerplate code:

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

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

Simply copy and paste that in to a UICollectionViewFlowLayout- you're done.

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

Full working solution to copy and paste:

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

This is the whole thing:

这是整件事:

##代码##

enter image description here

在此处输入图片说明

And finally...

最后...

Give thanks to @AlexShubin above who first clarified this!

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

回答by mklb

I had the same problem, Give the Cocoapod UICollectionViewLeftAlignedLayouta try. Just include it in your project and initialize it like this:

我遇到了同样的问题,试试Cocoapod UICollectionViewLeftAlignedLayout。只需将它包含在您的项目中并像这样初始化它:

##代码##

回答by Evan R

Building on Michael Sand's answer, I created a subclassed UICollectionViewFlowLayoutlibrary to do left, right, or full (basically the default) horizontal justification—it also lets you set the absolute distance between each cell. I plan on adding horizontal center justification and vertical justification to it, too.

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

https://github.com/eroth/ERJustifiedFlowLayout

https://github.com/eroth/ERJustifiedFlowLayout

回答by GregP

In swift. According to Michaels answer

在迅。根据迈克尔斯的回答

##代码##

回答by Ryan Poolos

Here is the original answer in Swift. It still works great mostly.

这是 Swift 中的原始答案。大多数情况下它仍然很好用。

##代码##

Exception: Autosizing Cells

例外:自动调整单元格

There is one big exception sadly. If you're using UICollectionViewFlowLayout's estimatedItemSize. Internally UICollectionViewFlowLayoutis changing things up a bit. I haven't tracked it entirely down but its clear its calling other methods after layoutAttributesForElementsInRectwhile self sizing cells. From my trial and error I found it seems to call layoutAttributesForItemAtIndexPathfor each cell individually during autosizing more often. This updated LeftAlignedFlowLayoutworks great with estimatedItemSize. It works with static sized cells as well, however the extra layout calls leads me to use the original answer anytime I don't need autosizing cells.

遗憾的是,有一个很大的例外。如果您使用UICollectionViewFlowLayout的是estimatedItemSize. 内部UICollectionViewFlowLayout正在改变一些事情。我还没有完全追踪它,但很明显它layoutAttributesForElementsInRect在自我调整单元格大小之后调用了其他方法。从我的反复试验中,我发现它似乎layoutAttributesForItemAtIndexPath在自动调整大小期间更频繁地单独调用每个单元格。此更新LeftAlignedFlowLayout适用于estimatedItemSize. 它也适用于静态大小的单元格,但是额外的布局调用会导致我在不需要自动调整单元格大小的任何时候使用原始答案。

##代码##

回答by niku

If anyone of you facing issue - some of the cells that's on the right of the collection view exceeding the bounds of the collection view. Then please use this -

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

##代码##

Use INTin place of comparing CGFloatvalues.

使用INT代替比较CGFloat值。