5

I have a collectionView and the direction is set to Horizontal and every cell just contain a UIlabel and it's using autolayout to determine the width of the cell.

for example, there are three cells and it's default behaviour.

|[x] [xxx] [xxxxxxxxx] |

but the requirement is to center align these three cells if the total width of all cells doesn't exceed the width of the screen.

the left inset for the first cell should be

leftInset = (screen width - (first cell width + second cell width + third cell width + total spaces between the three cells) /) 2

the result would be

| [x] [xxx] [xxxxxxxxx] |

I have found a great answer for the cells with fixed width from How to center horizontally UICollectionView Cells?, but doesn't work on self-size one.

would be much appreciated if you would help.

Eric Yuan
  • 3,324
  • 1
  • 14
  • 14
  • I think you will need to look into creating your own UICollectionViewLayout that will position the cells as you require. This tutorial shows the basics of how to do it [link](https://www.raywenderlich.com/164608/uicollectionview-custom-layout-tutorial-pinterest-2) – Upholder Of Truth Dec 27 '17 at 22:09
  • I am stuck with the same issue. – Umesh Sharma Jan 09 '18 at 08:57

1 Answers1

5

Really annoying, that Apple doesn't provide ready-to-use layouts for such things...

In the end I came up with following flow layout:

class HorizontallyCenteredCollectionViewFlowLayout: UICollectionViewFlowLayout {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.scrollDirection = .horizontal
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
        guard let collectionView = self.collectionView,
            let rightmostEdge = attributes.map({ $0.frame.maxX }).max() else { return attributes }

        let contentWidth = rightmostEdge + self.sectionInset.right
        let margin = (collectionView.bounds.width - contentWidth) / 2

        if margin > 0 {
            let newAttributes: [UICollectionViewLayoutAttributes]? = attributes
                .compactMap {
                    let newAttribute = $0.copy() as? UICollectionViewLayoutAttributes
                    newAttribute?.frame.origin.x += margin
                    return newAttribute
            }

            return newAttributes
        }

        return attributes
    }
}

This basically consists of the following steps:

  1. calculate the contentWidth
  2. calculate the (left+right) margin between the contentWidth and the collectionViews width
  3. apply the margin if necessary*, which leads to a entering of the content

* Negative margin means the content is bigger than the collectionView and the attributes should therefore stay unchanged => scrollable and left aligned as one would expect.

d4Rk
  • 6,622
  • 5
  • 46
  • 60
  • 1
    This works pretty well for me, but it seems to assume that `last.frame.maxX` is the rightmost edge, which isn't always true. I think it would be more robust to use `attributes.map { $0.frame.maxX }.max()`? – Tom Mar 19 '19 at 19:42
  • @Tom Thx for this notion, just updated my answer accordingly. – d4Rk Mar 20 '19 at 11:50