Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 263 Vote(s) - 3.66 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Left Align Cells in UICollectionView

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

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.


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 `forEach` call:


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

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

#12
Here is my journey to find the best code that works with Swift 5. I have joined couple of answers from this thread and some other threads to solve warnings and issues that I faced. I had a warning and some abnormal behavior when scrolling through my collection view. The console prints the following:

> This is likely occurring because the flow layout "xyz" is modifying attributes returned by UICollectionViewFlowLayout without copying them.

Another issue I faced is that some lengthy cells are getting cropped at the right side of the screen. Also, I was setting the section insets and the minimumInteritemSpacing in the delegate functions which resulted in values were not reflected in the custom class. The fix for that was setting those attributes to an instance of the layout before applying it to my collection view.

Here's how I used the layout for my collection view:

let layout = LeftAlignedCollectionViewFlowLayout()
layout.minimumInteritemSpacing = 5
layout.minimumLineSpacing = 7.5
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
super.init(frame: frame, collectionViewLayout: layout)


Here's the flow layout class

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }

var leftMargin = sectionInset.left
var maxY: CGFloat = -1.0
attributes?.forEach { layoutAttribute in
guard layoutAttribute.representedElementCategory == .cell else {
return
}

if Int(layoutAttribute.frame.origin.y) >= Int(maxY) || layoutAttribute.frame.origin.x == sectionInset.left {
leftMargin = sectionInset.left
}

if layoutAttribute.frame.origin.x == sectionInset.left {
leftMargin = sectionInset.left
}
else {
layoutAttribute.frame.origin.x = leftMargin
}

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

return attributes
}
}
Reply

#13
Based on all answers.
Works for leftToRight and 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
}
}

Reply

#14
If your minimum deployment target is iOS 13, I highly suggest you take advantage of the **Compositional Layout** (doc [here](

[To see links please register here]

), WWDC presentation [here](

[To see links please register here]

)).

I did try some of the top answers here initially. Unfortunately, we encountered an issue wherein some cells tend to disappear intermittently. To us, this happens after calling UICollectionView's `reloadData` function. It's also important to note that our cells have variable width, a.k.a auto-sizing.

Let me show you an example. Let's say we need to display a page with a list of keyword bubbles.

[![enter image description here][1]][1]

Here's what you might need in order to accomplish that using Compositional Layout.

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

So as you can see, it's very straightforward.


[1]:
Reply

#15
Most of the solutions on this page are way too complicated. The easiest way to left justify them, even if there is only 1 cell, is to return the following edge insets:

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

Reply

#16
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 _y_ value **only work for single-line layouts**. They fail for collection view layouts with multiple lines.
- Solutions that _do_ check the _y_ value like [Angel García Olloqui's answer][1] **only 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 docs][2] state:

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

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

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

In order to address these issues I've created a `UICollectionViewFlowLayout` subclass that follows a similar idea as suggested by [_matt_][3] and [_Chris Wagner_][4] in their answers to a similar question. It can either align the cells

<kbd>⬅︎</kbd> **left**:

[![Left-aligned layout][5]][5]

or <kbd>➡︎</kbd> **right**:

[![Right-aligned layout][6]][6]

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

You can simply download it here:<br />
###

[To see links please register here]


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 `collectionViewLayout` property:

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

yourCollectionView.collectionViewLayout = alignedFlowLayout
```

(It's also available on [Cocoapods][7].)

---

### 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 `rect` and then call the first function for every index path to retrieve the correct frames:

```swift
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 code][8] for its implementation.)

So now the only thing we have to do is to implement the `layoutAttributesForItem(at indexPath: IndexPath)` function properly. The super class `UICollectionViewFlowLayout` already 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.

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 `maxX` value of the previous cell's frame and that's the `origin.x` value for the current cell's frame.

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 _i_ is in 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 over the width of the whole collection view. As the `UICollectionViewFlowLayout` centers all cells vertically every cell in the same line _must_ intersect with this rectangle.

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

* If it does intersect, the cell with index _i_ is not the left most cell in the line.<br />
→ 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 _i_ is the left most cell in the line. <br />
→ Move the cell to the left edge of the collection view (without changing its vertical position).

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 _idea_ and you can always check my implementation in the [source code][8]. (It's 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 guess this is the longest answer I've ever written on Stackoverflow. I hope this helps. 😉


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

[4]:

[To see links please register here]

[5]:

[6]:

[7]:

[To see links please register here]

[8]:

[To see links please register here]

Reply

#17
Edited Angel García Olloqui's answer to respect `minimumInteritemSpacing` from delegate's `collectionView(_:layout:minimumInteritemSpacingForSectionAt:)`, if it implements it.
```swift
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

let delegate = collectionView?.delegate as? UICollectionViewDelegateFlowLayout
let spacing = delegate?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: 0) ?? minimumInteritemSpacing

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

return attributes
}
```
Reply

#18
Based on all answers, I change a bit and it works good for me
```swift
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
|| layoutAttribute.frame.origin.x == sectionInset.left {
leftMargin = sectionInset.left
}

if layoutAttribute.frame.origin.x == sectionInset.left {
leftMargin = sectionInset.left
}
else {
layoutAttribute.frame.origin.x = leftMargin
}

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

return attributes
}
```
Reply

#19
In swift. According to Michaels answer

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

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

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

## Exception: Autosizing Cells ##
There is one big exception sadly. If you're using `UICollectionViewFlowLayout`'s `estimatedItemSize`. Internally `UICollectionViewFlowLayout` is changing things up a bit. I haven't tracked it entirely down but its clear its calling other methods after `layoutAttributesForElementsInRect` while self sizing cells. From my trial and error I found it seems to call `layoutAttributesForItemAtIndexPath` for each cell individually during autosizing more often. This updated `LeftAlignedFlowLayout` works 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.
```swift
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
}
}
```
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through