Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to center horizontally self-sizing UICollectionView Cells?

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.

like image 710
Eric Yuan Avatar asked Dec 27 '17 21:12

Eric Yuan


1 Answers

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.

like image 178
d4Rk Avatar answered Sep 20 '22 16:09

d4Rk