How to center align the cells of a UICollectionView in Swift3.0?
Asked Answered
J

12

15

Description:

Answer for Objective-C and Swift2.0: How to center align the cells of a UICollectionView?

I usually would try to convert the Swift2.0 answer to the Swift3.0 solution, however the method:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
        let edgeInsets = (screenWight - (CGFloat(elements.count) * 50) - (CGFloat(elements.count) * 10)) / 2
        return UIEdgeInsetsMake(0, edgeInsets, 0, 0);
    }

doesn't seem to exist in Swift3.0,and the only other method I found that seems useful is:

func collectionView(_ collectionView: UICollectionView, transitionLayoutForOldLayout fromLayout: UICollectionViewLayout, newLayout toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout {
        <#code#>
}

but I am unsure how to implement it correctly.

Question:

How to center align the cells of a UICollectionView in Swift3.0?

(A simple and general solution for iOS and tvOS would be perfect)

Jacobsen answered 14/11, 2016 at 9:48 Comment(0)
J
23

This ended up being the solution I used. Read the code comments for a better understanding. Swift 5

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

    //Where elements_count is the count of all your items in that
    //Collection view...
    let cellCount = CGFloat(elements_count)

    //If the cell count is zero, there is no point in calculating anything.
    if cellCount > 0 {
        let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
        let cellWidth = flowLayout.itemSize.width + flowLayout.minimumInteritemSpacing

        //20.00 was just extra spacing I wanted to add to my cell.
        let totalCellWidth = cellWidth*cellCount + 20.00 * (cellCount-1)
        let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right

        if (totalCellWidth < contentWidth) {
            //If the number of cells that exists take up less room than the
            //collection view width... then there is an actual point to centering them.

            //Calculate the right amount of padding to center the cells.
            let padding = (contentWidth - totalCellWidth) / 2.0
            return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding)
        } else {
            //Pretty much if the number of cells that exist take up
            //more room than the actual collectionView width, there is no
            // point in trying to center them. So we leave the default behavior.
            return UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 40)
        }
    }
    return UIEdgeInsets.zero
}
Jacobsen answered 12/12, 2016 at 8:24 Comment(3)
I coped this is exactly (swift 4), compiles, but the cells are not centered on the screen?Cleave
@AlexKornhauser it clearly states it's for Swift 3.0. This is the accepted answer because it worked perfectly fine for me. An accepted answer does not have to be the "best" answer, just the one that answer's the OP's question. Since I am the OP, I obviously chose what worked for me. Feel free to try jbouaziz updated version. The upvotes are better in finding the "best" answer. cheers.Jacobsen
You can get the cellCount value like this: let cellCount = CGFloat(collectionView.numberOfItems(inSection: section))Infrasonic
V
20

While rottenoats answer is great, it has an extra spacing bug and doesn't use much of the available swift 3 syntax. I've fixed that and reduced the number of dependencies to local variables.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

    // Make sure that the number of items is worth the computing effort.
    guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout,
        let dataSourceCount = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: section),
        dataSourceCount > 0 else {
            return .zero
    }


    let cellCount = CGFloat(dataSourceCount)
    let itemSpacing = flowLayout.minimumInteritemSpacing
    let cellWidth = flowLayout.itemSize.width + itemSpacing
    var insets = flowLayout.sectionInset


    // Make sure to remove the last item spacing or it will
    // miscalculate the actual total width.
    let totalCellWidth = (cellWidth * cellCount) - itemSpacing
    let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right


    // If the number of cells that exist take up less room than the
    // collection view width, then center the content with the appropriate insets.
    // Otherwise return the default layout inset.
    guard totalCellWidth < contentWidth else {
        return insets
    }


    // Calculate the right amount of padding to center the cells.
    let padding = (contentWidth - totalCellWidth) / 2.0
    insets.left = padding
    insets.right = padding
    return insets
}

N.B.: This snippet only works for an horizontal scrolling but can easily be adjusted.

Valais answered 12/6, 2017 at 11:7 Comment(0)
H
8

Slight adaptation of @rottenoats answer. This is more generic.

Most importantly remember to make your view controller conform to the UICollectionViewDelegateFlowLayout protocol.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

    guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else {
        return .zero
    }

    let cellCount = CGFloat(collectionView.numberOfItems(inSection: section))

    if cellCount > 0 {
        let cellWidth = flowLayout.itemSize.width + flowLayout.minimumInteritemSpacing

        let totalCellWidth = cellWidth * cellCount
        let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right - flowLayout.headerReferenceSize.width - flowLayout.footerReferenceSize.width

        if (totalCellWidth < contentWidth) {
            let padding = (contentWidth - totalCellWidth + flowLayout.minimumInteritemSpacing) / 2.0
            return UIEdgeInsetsMake(0, padding, 0, 0)
        }
    }

    return .zero
}
Hare answered 23/8, 2017 at 10:25 Comment(1)
The best answer! Thanks a lot!Integrated
D
4

Here's a recent solution, works for Swift 5.0

extension YourViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        insetForSectionAt section: Int) -> UIEdgeInsets {
        let itemWidth = 80
        let spacingWidth = 4
        let numberOfItems = collectionView.numberOfItems(inSection: section)
        let cellSpacingWidth = numberOfItem * spacingWidth
        let totalCellWidth = numberOfItems * itemWidth + cellSpacingWidth
        let inset = (collectionView.layer.frame.size.width - CGFloat(totalCellWidth)) / 2
        return UIEdgeInsets(top: 5, left: inset, bottom: 5, right: inset)
    }
}
Decoration answered 11/9, 2019 at 12:29 Comment(0)
I
3

The method that you are looking for is present with a different signature in Swift 3. The new signature is this:

optional public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets

P.S: This method is present in UICollectionViewDelegateFlowLayout protocol.
Hope this would help.

Interlanguage answered 14/11, 2016 at 10:10 Comment(0)
E
3

Assuming that you are conforming to UICollectionViewDelegateFlowLayout, it should be:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    let edgeInsets = (screenWight - (CGFloat(elements.count) * 50) - (CGFloat(elements.count) * 10)) / 2
    return UIEdgeInsets(top: 0, left: edgeInsets, bottom: 0, right: 0)
}
Encircle answered 14/11, 2016 at 10:20 Comment(0)
I
2

This code should center horizontally any type of collection view even with extra items in Swift 4.0:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let cellWidth: CGFloat = flowLayout.itemSize.width
    let cellSpacing: CGFloat = flowLayout.minimumInteritemSpacing
    let cellCount = CGFloat(collectionView.numberOfItems(inSection: section))
    var collectionWidth = collectionView.frame.size.width
    if #available(iOS 11.0, *) {
        collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
    }
    let totalWidth = cellWidth * cellCount + cellSpacing * (cellCount - 1)
    if totalWidth <= collectionWidth {
        let edgeInset = (collectionWidth - totalWidth) / 2
        return UIEdgeInsetsMake(flowLayout.sectionInset.top, edgeInset, flowLayout.sectionInset.bottom, edgeInset)
    } else {
        return flowLayout.sectionInset
    }
}

Don't forget if you are not subclassing UICollectionViewController, make sure your class conforms to UICollectionViewDelegateFlowLayout protocol

Indecorum answered 3/12, 2018 at 6:18 Comment(0)
B
1
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {

let allCellWidth = KcellWidth * numberOfCell
let cellSpacingWidth = CellSpacing * (numberOfCell - 1)

let leftInset = (collectionViewWidth - CGFloat(allCellWidth + cellSpacingWidth)) / 2
let rightInset = leftInset

return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}
Bobo answered 29/5, 2017 at 4:22 Comment(0)
F
0

You need to conform to UICollectionViewDelegateFlowLayout protocol to find the collectionView(_:layout:insetForSectionAt:) method, it is defined there. the following implementation works fine in swift 4 for a horizontal collectionView

extension YourViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        let cellWidth = CGFloat(integerLiteral: YourCellsWidth)
        let count = self.YourCollectionViewDataSource.count
        let top = CGFloat(integerLiteral: 0)    // I don't need to set top and bottom spacing
        let cellsAccumulatedWidth = CGFloat(integerLiteral: count) * cellWidth
        let cellsSpacingAccumulatedWidth = CGFloat(integerLiteral: (count - 1) * 5)
        let left = (self.collectionView.frame.size.width - cellsAccumulatedWidth - cellsSpacingAccumulatedWidth) / 2
        let bottom = top
        let right = left

        return UIEdgeInsetsMake(top, left, bottom, right);
    }
}
Fluency answered 22/10, 2018 at 13:4 Comment(0)
A
0

Use this solution for horizontal scrolling with the center align.

let totalCellWidth = Int(collectionView.frame.height * CGFloat(numberOfItems()))

        // if total width is greater than collection's width, then no need to align center
        if totalCellWidth > Int(collectionView.frame.width) {
            return UIEdgeInsets.zero
        }

        let totalSpacingWidth = cellSpacing * (numberOfItems() - 1)
        let leftInset = (collectionView.frame.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
        let rightInset = leftInset
        return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
Alcot answered 4/3, 2020 at 12:5 Comment(0)
C
0

I think the easiest solution in case your collectionView has one row is to substitute it by a stackView inside a scrollview so you can set the spacing you want between the views. Besides, if you want some padding you will be able apply it.

Chery answered 17/9, 2020 at 15:46 Comment(0)
F
0

My cell sizes were dynamic and different thus rottenoats' answer didn't work for me. Here's how I've ended up doing it:

public override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    calculateAndSetCollectionInsets()
}

private func calculateAndSetCollectionInsets() {
    let padding = (collectionView.frame.width - collectionView.contentSize.width) / 2
    guard padding > 0 else {
        collectionView.contentInset = .zero
        return
    }
    collectionView.contentInset = UIEdgeInsets(horizontal: padding, vertical: 0)
}
Forefather answered 11/1 at 16:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.