There are many great ideas in answering this question. However, most of them have some disadvantages:
- Solutions that do not check the value of cell y work only for single-line layouts . They are not suitable for multi-line collection layouts.
- Solutions that check the y value, such as Angel GarcΓa Olloqui answer, work only if all cells have the same height . They are not suitable for variable height cells.
Most solutions only override the layoutAttributesForElements(in rect: CGRect) . They do not override layoutAttributesForItem(at indexPath: IndexPath) . . This is a problem because the collection view periodically calls the last function to retrieve the layout attributes for a specific index path. If you do not return the proper attributes from this function, you are likely to come across all kinds of visual errors, for example. during insertion and deletion animation of cells or when using self-contained cells by setting the layout view of the estimatedItemSize collection. Apple docs :
It is assumed that each custom layout object will implement the layoutAttributesForItemAtIndexPath: method.
Many decisions also make assumptions about the rect parameter, which is passed to the layoutAttributesForElements(in rect: CGRect) . For example, many of them are based on the assumption that rect always starts at the beginning of a new line, which is optional.
So in other words:
Most of the solutions offered on this page work for some specific applications, but they do not work properly in any situation.
AlignedCollectionViewFlowLayout
To solve these problems, I created a subclass of UICollectionViewFlowLayout , which follows a similar idea proposed by matt and Chris Wagner in their answers to a similar question. It can either align cells
β¬
οΈ left :

or β‘οΈ on the right :

and additionally offers options to vertically align cells in their respective rows (if they change in height).
You can simply download it here:
Use is straightforward and is explained in the README file. You basically create an AlignedCollectionViewFlowLayout instance, specify the desired alignment and assign it to your collectionViewLayout collectionViewLayout view:
let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .top) yourCollectionView.collectionViewLayout = alignedFlowLayout
(It is also available on Cocoapods .)
How it works (for left-aligned cells):
The concept here is to rely solely on the layoutAttributesForItem(at indexPath: IndexPath) . In layoutAttributesForElements(in rect: CGRect) we just get the pointer paths of all cells in rect , and then call the first function for each index path to get the correct frames:
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 the layout attributes in the array. You can examine the source code to implement it.)
So now the only thing we need to do is to correctly implement the layoutAttributesForItem(at indexPath: IndexPath) . The superclass UICollectionViewFlowLayout already puts the correct number of cells in each row, so we only need to shift them to the left within their corresponding row. The difficulty lies in calculating the amount of space that you need to move each cell to the left.
Since we want to have a fixed spacing between cells, the basic idea is to simply assume that the previous cell (the cell to the left of the cell that is currently laid out) is already set correctly. Then we only need to add the distance between the cells to the maxX value of the previous cell frame and the origin.x value for the current cell frame.
Now we only need to know when we have reached the beginning of the row so that we do not align the cell next to the cell in the previous row. (This will lead not only to a bad layout, but also to being too lagging.) Thus, we need a recursive anchor. The approach I use to search for this recursive anchor is as follows:
To find out if the cell in the index is on the same line as the cell with index 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 across the width of the entire collection. Since UICollectionViewFlowLayout centers all the cells vertically, each cell on the same row should intersect with this rectangle.
Thus, I just check if the cell with index i-1 intersects this rectangle of the line created from the cell with index i.
If it intersects, the cell with index i is not the leftmost cell in the row.
β Get the previous frame of the cell (with index i-1) and move the current cell next to it.
If it does not intersect, the cell with index i is the leftmost cell in the row.
β Move the cell to the left edge of the collection view (without changing its vertical position).
I will not post the actual implementation of the layoutAttributesForItem(at indexPath: IndexPath) here, because I think the most important part is to understand the idea, and you can always check my implementation in the source code . (This is a little more complicated than explained here, because I also allow .right alignment and various vertical alignment options. However, it follows the same idea.)
Wow, I think this is the longest answer I've ever written on Stackoverflow. Hope this helps. π