How to center horizontally UICollectionView Cells?
IosSwiftIos Problem Overview
I have done some research, but I couldn't find any code example on how to center cells in a UICollectionView horizontally.
instead of the first cell being like this X00, I want it to be like this 0X0. is there any way to accomplish this?
EDIT:
to visualize what I want:
I need it to look like version B when there is only one element in the CollectionView. When I got more than one element, then it should be like version A but with more elements.
At the moment it looks like Version A when I have only 1 element, and I wonder how I can make it look like B.
Thanks for the help!
Ios Solutions
Solution 1 - Ios
Its not a good idea to use a library, if your purpose is only this i.e to centre align.
Better you can do this simple calculation in your collectionViewLayout function.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let totalCellWidth = CellWidth * CellCount
let totalSpacingWidth = CellSpacing * (CellCount - 1)
let leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Solution 2 - Ios
Swift 5.1
func centerItemsInCollectionView(cellWidth: Double, numberOfItems: Double, spaceBetweenCell: Double, collectionView: UICollectionView) -> UIEdgeInsets {
let totalWidth = cellWidth * numberOfItems
let totalSpacingWidth = spaceBetweenCell * (numberOfItems - 1)
let leftInset = (collectionView.frame.width - CGFloat(totalWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Swift 4.2
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let totalCellWidth = 80 * collectionView.numberOfItems(inSection: 0)
let totalSpacingWidth = 10 * (collectionView.numberOfItems(inSection: 0) - 1)
let leftInset = (collectionView.layer.frame.size.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Swift 3
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let totalCellWidth = 80 * collectionView.numberOfItems(inSection: 0)
let totalSpacingWidth = 10 * (collectionView.numberOfItems(inSection: 0) - 1)
let leftInset = (collectionView.layer.frame.size.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}
don't forget to add the protocol
UICollectionViewDelegateFlowLayout
Solution 3 - Ios
Try this for Swift 4
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth : CGFloat = 165.0
let numberOfCells = floor(collectionView.frame.size.width / cellWidth)
let edgeInsets = (collectionView.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
return UIEdgeInsetsMake(15, edgeInsets, 0, edgeInsets)
}
Add your cellWidth instead 165.0
Solution 4 - Ios
I use KTCenterFlowLayout for this, and it works great. It's a custom subclass of UICollectionViewFlowLayout
that centres cells as you want. (Note: this isn't a trivial thing to solve by posting some code, which is why I'm linking to a GitHub project!)
Solution 5 - Ios
An objective-C version of Darshan Patel's answer:
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
CGFloat totalCellWidth = kItemWidth * self.dataArray.count;
CGFloat totalSpacingWidth = kSpacing * (((float)self.dataArray.count - 1) < 0 ? 0 :self.dataArray.count - 1);
CGFloat leftInset = (self.bounds.size.width - (totalCellWidth + totalSpacingWidth)) / 2;
CGFloat rightInset = leftInset;
UIEdgeInsets sectionInset = UIEdgeInsetsMake(0, leftInset, 0, rightInset);
return sectionInset;
}
Solution 6 - Ios
You can use this extension (Swift 4).
It can center cells with if you collectionView
have layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
.
It work with any cells size and work perfectly when scrollDirection = .horizontal
public extension UICollectionView {
func centerContentHorizontalyByInsetIfNeeded(minimumInset: UIEdgeInsets) {
guard let layout = collectionViewLayout as? UICollectionViewFlowLayout,
layout.scrollDirection == .horizontal else {
assertionFailure("\(#function): layout.scrollDirection != .horizontal")
return
}
if layout.collectionViewContentSize.width > frame.size.width {
contentInset = minimumInset
} else {
contentInset = UIEdgeInsets(top: minimumInset.top,
left: (frame.size.width - layout.collectionViewContentSize.width) / 2,
bottom: minimumInset.bottom,
right: 0)
}
}
}
final class Foo: UIViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.centerContentHorizontalyByInsetIfNeeded(minimumInset: yourDefaultInset)
}
}
Hope it's help you!
Solution 7 - Ios
Slightly modifying @Safad Funy's answer, this is what worked for me in the lattest version of Swift & iOS. In this case I wanted the cells's width to be a third of the size of the collection view.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let totalCellWidth = Int(collectionView.layer.frame.size.width) / 3 * collectionView.numberOfItems(inSection: 0)
let totalSpacingWidth = (collectionView.numberOfItems(inSection: 0) - 1)
let leftInset = (collectionView.layer.frame.size.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}
Solution 8 - Ios
General solution for flowlayout that centers the pages if they are less than the width and aligns left if there are more
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
// Centering if there are fever pages
CGSize itemSize = [(UICollectionViewFlowLayout *)collectionViewLayout itemSize];
CGFloat spacing = [(UICollectionViewFlowLayout *)collectionViewLayout minimumLineSpacing];
NSInteger count = [self collectionView:self numberOfItemsInSection:section];
CGFloat totalCellWidth = itemSize.width * count;
CGFloat totalSpacingWidth = spacing * ((count - 1) < 0 ? 0 : count - 1);
CGFloat leftInset = (self.bounds.size.width - (totalCellWidth + totalSpacingWidth)) / 2;
if (leftInset < 0) {
UIEdgeInsets inset = [(UICollectionViewFlowLayout *)collectionViewLayout sectionInset];
return inset;
}
CGFloat rightInset = leftInset;
UIEdgeInsets sectionInset = UIEdgeInsetsMake(0, leftInset, 0, rightInset);
return sectionInset;
}
Swift version (converted from ObjC)
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
// Centering if there are fever pages
let itemSize: CGSize? = (collectionViewLayout as? UICollectionViewFlowLayout)?.itemSize
let spacing: CGFloat? = (collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing
let count: Int = self.collectionView(self, numberOfItemsInSection: section)
let totalCellWidth = (itemSize?.width ?? 0.0) * CGFloat(count)
let totalSpacingWidth = (spacing ?? 0.0) * CGFloat(((count - 1) < 0 ? 0 : count - 1))
let leftInset: CGFloat = (bounds.size.width - (totalCellWidth + totalSpacingWidth)) / 2
if leftInset < 0 {
let inset: UIEdgeInsets? = (collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset
return inset!
}
let rightInset: CGFloat = leftInset
let sectionInset = UIEdgeInsets(top: 0, left: Float(leftInset), bottom: 0, right: Float(rightInset))
return sectionInset
}
Solution 9 - Ios
Swift 4.2 (Horizontally and Vertically). It's small upgrade of Pink Panther code and big thanks him!
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let cellWidth: CGFloat = flowLayout.itemSize.width
let cellHieght: CGFloat = flowLayout.itemSize.height
let cellSpacing: CGFloat = flowLayout.minimumInteritemSpacing
let cellCount = CGFloat(collectionView.numberOfItems(inSection: section))
var collectionWidth = collectionView.frame.size.width
var collectionHeight = collectionView.frame.size.height
if #available(iOS 11.0, *) {
collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
collectionHeight -= collectionView.safeAreaInsets.top + collectionView.safeAreaInsets.bottom
}
let totalWidth = cellWidth * cellCount + cellSpacing * (cellCount - 1)
let totalHieght = cellHieght * cellCount + cellSpacing * (cellCount - 1)
if totalWidth <= collectionWidth {
let edgeInsetWidth = (collectionWidth - totalWidth) / 2
print(edgeInsetWidth, edgeInsetWidth)
return UIEdgeInsets(top: 5, left: edgeInsetWidth, bottom: flowLayout.sectionInset.top, right: edgeInsetWidth)
} else {
let edgeInsetHieght = (collectionHeight - totalHieght) / 2
print(edgeInsetHieght, edgeInsetHieght)
return UIEdgeInsets(top: edgeInsetHieght, left: flowLayout.sectionInset.top, bottom: edgeInsetHieght, right: flowLayout.sectionInset.top)
}
}
Make sure your class conforms to UICollectionViewDelegateFlowLayout protocol
Solution 10 - Ios
Here is the newer version for Swift 5 which also works fine when the cells are more than one row:
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
var cellCount = CGFloat(collectionView.numberOfItems(inSection: section))
var collectionWidth = collectionView.frame.size.width
var totalWidth: CGFloat
if #available(iOS 11.0, *) {
collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
}
repeat {
totalWidth = cellWidth * cellCount + cellSpacing * (cellCount - 1)
cellCount -= 1
} while totalWidth >= collectionWidth
if (totalWidth > 0) {
let edgeInset = (collectionWidth - totalWidth) / 2
return UIEdgeInsets.init(top: flowLayout.sectionInset.top, left: edgeInset, bottom: flowLayout.sectionInset.bottom, right: edgeInset)
} else {
return flowLayout.sectionInset
}
}
Please make sure your class conforms to UICollectionViewDelegateFlowLayout
protocol.
Solution 11 - Ios
Swift 4
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth: CGFloat = 170.0 // Your cell width
let numberOfCells = floor(view.frame.size.width / cellWidth)
let edgeInsets = (view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
return UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets)
}
}
Solution 12 - Ios
For the people who only want to add a padding (top, left, bottom, right):
Add the protocol UICollectionViewDelegateFlowLayout
This example shows a padding left and right with 40.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 40, 0, 40)
}
Solution 13 - Ios
SWIFT 4.2
private lazy var contentView: UICollectionView = {
let layoutView: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layoutView.scrollDirection = .horizontal
layoutView.minimumInteritemSpacing = 0
layoutView.minimumLineSpacing = 5
let collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: layoutView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
collectionView.registerCell(Cell.self)
collectionView.backgroundColor = .clear
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
//
extension CustomCollectionView: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width*4/5, height: collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth : CGFloat = collectionView.frame.width*4/5
let numberOfCells = floor(collectionView.frame.width / cellWidth)
let edgeInsets = (collectionView.frame.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
return UIEdgeInsets(top: 0, left: edgeInsets, bottom: 0, right: edgeInsets)
}
}
Solution 14 - Ios
If you're using Darshan Patel's answer, you can also perform the calculation inside UICollectionViewFlowLayout subclass.
class Layout: UICollectionViewFlowLayout {
override init() {
super.init()
scrollDirection = .horizontal
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError()
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView,
collectionView.numberOfSections != 0 else { return }
minimumInteritemSpacing = 5
itemSize = CGSize(width: 30, height: 30)
sectionInsetReference = .fromSafeArea
let numberOfItems = collectionView.numberOfItems(inSection: 0)
let totalCellWidth = itemSize.width * CGFloat(numberOfItems)
let totalSpacingWidth = minimumInteritemSpacing * CGFloat(numberOfItems - 1)
let leftInset = (collectionView.bounds.maxX - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
sectionInset = UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: leftInset)
}
}
Solution 15 - Ios
You can try my solution it works fine,
func refreshCollectionView(_ count: Int) {
let collectionViewHeight = collectionView.bounds.height
let collectionViewWidth = collectionView.bounds.width
let numberOfItemsThatCanInCollectionView = Int(collectionViewWidth / collectionViewHeight)
if numberOfItemsThatCanInCollectionView > count {
let totalCellWidth = collectionViewHeight * CGFloat(count)
let totalSpacingWidth: CGFloat = CGFloat(count) * (CGFloat(count) - 1)
// leftInset, rightInset are the global variables which I am passing to the below function
leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2;
rightInset = -leftInset
} else {
leftInset = 0.0
rightInset = -collectionViewHeight
}
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}
Solution 16 - Ios
The accepted answer is the right answer but if your totalCellWidth
is less than the CollectionView
's width
, but just to guard against this you can do as below.
if (leftInset > 0) {
return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
} else {
return UIEdgeInsetsMake(0, 10, 0, 10)
}
Solution 17 - Ios
This code should center horizontally collection view even in Swift 4.0 without any modification:
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
}
}
Make sure your class conforms to UICollectionViewDelegateFlowLayout
protocol
Solution 18 - Ios
If there is only room for one cell per group, a leading:
and trailing:
of .flexible(0)
will center the cell horizontally:
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
leading: .flexible(0), top: nil,
trailing: .flexible(0), bottom: nil
)
Solution 19 - Ios
I ended up taking a completely different approach here, which I believe is worth mentioning.
I set a constraint on my collection view to be horizontally aligned in the center. Then I set another constraint that specifies the width. I created an outlet for the width constraint inside of my viewController that holds the collection view. Then, when my data source is changed and I am updating the collection view, I take the count of the cells and do a (very similar) calculation to reset the width.
let newWidth = (items.count * cellWidth) + (items.count * cellSpacing)
Then I set the constraint outlet's .constant
value to the calculation result and autolayout does the rest.
This may conflict with the `UICollectionViewDelegateFlowLayout, but this worked perfectly for me to create a left-justified collection view. Without a delegate, it only seems to work when the cells filled up the majority of the view.
Solution 20 - Ios
I have similar situation in project, and I fixed it by referring UPCarouselFlowLayout
I think it support swift 5
version
Look at the code implementation in
override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
Solution 21 - Ios
the simplest way is to set collection view estimate size to None in storyboard or with code layout.estimatedItemSize = CGSize.zero
Solution 22 - Ios
I used that code in a project. It centers the collectionView horizontally and vertically in both direction .horizontal
and .vertical
using the insets of section. Respects the spacing and the original inset of the section if set.
Code to use in the delegate UICollectionViewDelegateFlowLayout
so we have access to all the properties we need to retrieve from the UIcollectionView
or set in the storyboard for reusability.
// original function of the delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
// casting the layout as a UICollectionViewFlowLayout to have access to the properties of items for reusability - you could also link the real one from the storyboard with an outlet
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
// getting all the properties we need
let itemWidth = flowLayout.itemSize.width
let itemHeight = flowLayout.itemSize.height
let interSpacing = flowLayout.minimumInteritemSpacing
let lineSpacing = flowLayout.minimumLineSpacing
// getting the size of the collectionView
let collectionWidth = collectionView.bounds.width
let collectionHeight = collectionView.bounds.height
// getting the direction to choose how to align the collection
let direction = flowLayout.scrollDirection
// you don't want to have an item greater than the collection
guard (itemWidth < collectionWidth && direction == .vertical) || (itemHeight < collectionHeight && direction == .horizontal) else {
print("Really?")
return UIEdgeInsets(top: flowLayout.sectionInset.top, left: flowLayout.sectionInset.left, bottom: flowLayout.sectionInset.bottom, right: flowLayout.sectionInset.right)
}
// getting the number of item in the current section
let totalItemCount = CGFloat(collectionView.numberOfItems(inSection: section))
// setting number of item in a row to the max number of items that can fit in a row without spacing or to the number of items in the section if less than the max
var itemCountInRow = totalItemCount < (collectionWidth / itemWidth).rounded(.towardZero) ? totalItemCount : (collectionWidth / itemWidth).rounded(.towardZero)
// how many max row can we have
var countOfRow = totalItemCount < (collectionHeight / itemHeight).rounded(.towardZero) ? totalItemCount : (collectionHeight / itemHeight).rounded(.towardZero)
// calculating the total width of row by multiplying the number of items in the row by the width of item and adding the spacing multiplied by the number of item minus one
var totalWidthOfRow:CGFloat {
get{
return (itemWidth * itemCountInRow) + (interSpacing * (itemCountInRow - 1))
}
}
// calculating the total height of row by multiplying the number of row by the height of item and adding the spacing multiplied by the number of row minus one
var totalHeightOfRow:CGFloat {
get{
return (itemHeight * countOfRow) + (lineSpacing * (countOfRow - 1))
}
}
// first we set the inset to the default
var edgeInsetLeft = flowLayout.sectionInset.left
var edgeInsetTop = flowLayout.sectionInset.top
if direction == .vertical {
// while the width of row with original margin is greater than the width of the collection we drop one item until it fits
while totalWidthOfRow > collectionWidth || ((collectionWidth - totalWidthOfRow) / 2) < flowLayout.sectionInset.left {
// droping an item to fit in the row
itemCountInRow -= 1
}
// calculating the number of rows in collectionView by dividing the number of items by the number of items in a row
countOfRow = (totalItemCount / (itemCountInRow)).rounded(.up)
} else {
itemCountInRow = (totalItemCount / countOfRow).rounded(.up)
// while the height of row with original marginis greater than the height of the collection we drop one row until it fits
while totalHeightOfRow >= collectionHeight || ((collectionHeight - totalHeightOfRow) / 2) < flowLayout.sectionInset.top {
// droping an item to fit in the row
countOfRow -= 1
}
}
edgeInsetLeft = max(flowLayout.sectionInset.left, (collectionWidth - totalWidthOfRow) / 2)
edgeInsetTop = max(flowLayout.sectionInset.top, (collectionHeight - totalHeightOfRow) / 2)
// we don't specially need insets where the items are overflowing
let edgeInsetRight = direction == .vertical ? edgeInsetLeft : flowLayout.sectionInset.right
let edgeInsetBottom = direction == .horizontal ? edgeInsetTop : flowLayout.sectionInset.bottom
// returning the UIEdgeInsets
return UIEdgeInsets(top: edgeInsetTop, left: edgeInsetLeft, bottom: edgeInsetBottom, right: edgeInsetRight)
}
Hope it would help someone - it centers the section not the items inside the section, for more we have to subclass the UICollectionViewFlowLayout
or UICollectionViewLayout
as the mosaic example from Apple.
Solution 23 - Ios
There is one improvement in the above answer for when the data is more; Use following code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
var totalCellsWidth:CGFloat = 0
for index in 0..<numberOfCells{
let width = viewModel.getCellWidth(at: index) // get cell width is to dynamically calculate the width of the cell according to the text
totalCellsWidth += width
}
let cellSpacing:CGFloat = 8.0
let totalSpacingWidth = cellSpacing * CGFloat(viewModel.teamsCount - 1)
var leftInset = (collectionView.frame.width - (totalCellsWidth + totalSpacingWidth)) / 2
if leftInset < 0{
leftInset = 0
}
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Solution 24 - Ios
You have to define a custom UICollectionViewFlowLayout here as bellow.
class CenterAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let superArray = super.layoutAttributesForElements(in: rect) else { return nil }
guard let attributes = NSArray(array: superArray, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }
// Constants
let leftPadding: CGFloat = 8
let interItemSpacing = minimumInteritemSpacing
// Tracking values
var leftMargin: CGFloat = leftPadding // Modified to determine origin.x for each item
var maxY: CGFloat = -1.0 // Modified to determine origin.y for each item
var rowSizes: [[CGFloat]] = [] // Tracks the starting and ending x-values for the first and last item in the row
var currentRow: Int = 0 // Tracks the current row
attributes.forEach { layoutAttribute in
// Each layoutAttribute represents its own item
if layoutAttribute.frame.origin.y >= maxY {
// This layoutAttribute represents the left-most item in the row
leftMargin = leftPadding
// Register its origin.x in rowSizes for use later
if rowSizes.count == 0 {
// Add to first row
rowSizes = [[leftMargin, 0]]
} else {
// Append a new row
rowSizes.append([leftMargin, 0])
currentRow += 1
}
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + interItemSpacing
maxY = max(layoutAttribute.frame.maxY, maxY)
// Add right-most x value for last item in the row
rowSizes[currentRow][1] = leftMargin - interItemSpacing
}
// At this point, all cells are left aligned
// Reset tracking values and add extra left padding to center align entire row
leftMargin = leftPadding
maxY = -1.0
currentRow = 0
attributes.forEach { layoutAttribute in
// Each layoutAttribute is its own item
if layoutAttribute.frame.origin.y >= maxY {
// This layoutAttribute represents the left-most item in the row
leftMargin = leftPadding
// Need to bump it up by an appended margin
let rowWidth = rowSizes[currentRow][1] - rowSizes[currentRow][0] // last.x - first.x
let appendedMargin = (collectionView!.frame.width - leftPadding - rowWidth - leftPadding) / 2
leftMargin += appendedMargin
currentRow += 1
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + interItemSpacing
maxY = max(layoutAttribute.frame.maxY, maxY)
}
return attributes
}
}
Then add following to the relevant viewController's viewDidLoad or relevant method,
let layout = CenterAlignedCollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 200, height: 40)
self.collectionRateCustomer.collectionViewLayout = layout
Don't forget to inherit UICollectionViewDelegateFlowLayout
References, equaleyes.com and Alex Koshy's answer
Solution 25 - Ios
another approach is to divide the space you have by the number of cells you want to display, this way you will mimic the .FillEqually mode of stackViews
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width / CGFloat(collectionView.numberOfItems(inSection: indexPath.section)), height: 28)
}
don't forget to :
- set the view in the cell contentview to a fixed width, centered in it's container without trailing and leading constraint
- remove any minimum spacing for cells and lines in the inspector of the CollectionView, set them to 0 each
- provide a height in the snippet of code above, instead of 28 as it is right now
Solution 26 - Ios
In multiple rows, this worked for me:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth = 370
let cellSpaceing = 10
let totalCellWithSpace = cellWidth + cellSpaceing
let howManyCellsInRow = Int(Int(collectionView.layer.frame.size.width) / (totalCellWithSpace))
let inset = (collectionView.layer.frame.size.width - CGFloat(howManyCellsInRow * totalCellWithSpace)) / 2
return UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
}
Solution 27 - Ios
Swift 5 modification of the currently accepted answer that fixes the issues when your cells can't fit the screen width.
Make sure to replace cellWidth
and cellSpacing
with your own custom values.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
let cellCount = collectionView.numberOfItems(inSection: section)
let totalCellWidth = cellWidth * CGFloat(cellCount)
let totalSpacingWidth = cellSpacing * CGFloat(cellCount - 1)
let collectionViewInset = collectionView.contentInset.left + collectionView.contentInset.right
let inset = (collectionView.frame.width - collectionViewInset - totalCellWidth - totalSpacingWidth) / 2
return inset > 0
? UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
: UIEdgeInsets.zero
}
Solution 28 - Ios
I've dealed with the same problem and as a solution I've copied layout from this project and applied it to my UICollectionView as a UICollectionViewFlowLayout. You don't need apply the full library, just copy layout class.
Solution 29 - Ios
For swift 5.2
I made some adjustments to meet my requirements
- Not to scroll if the content didn't extend outside the screen
- Center if the content didn't extend outside the screen
- If the content did extend outside the screen make sure to start at index 0 and allow scrolling while indenting from the edge by 16 (easy to adjust)
func centerItemsInCollectionView(cellWidth: Double, numberOfItems: Double, spaceBetweenCell: Double, collectionView: UICollectionView) -> UIEdgeInsets {
let totalWidth = cellWidth * numberOfItems
let totalSpacingWidth = spaceBetweenCell * (numberOfItems - 1)
let leftInset = (collectionView.frame.width - CGFloat(totalWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
if leftInset < 0 {
collectionView.isScrollEnabled = true
return UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
}
collectionView.isScrollEnabled = false
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Use this by doing this in your view controller
extension BaseViewController : UICollectionViewDelegateFlowLayout {
then you can call
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return centerItemsInCollectionView(cellWidth: 70, numberOfItems: 3, spaceBetweenCell: 25, collectionView: collectionView)
}
keep in mind if you want to set your own size for your cells you need to set the collectionView Estimated Size to none in storyboard and set your cells size to custom in storyboard
then you can call
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.init(width:60, height: 60)
}
if you are struggling to get methods to get called it's good to get all your delegates listened for
extension BaseViewController : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
and obviously don't forget to set your delegates, simply extending them won't do the trick.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
...
Solution 30 - Ios
I think you need to centre cell , so instead of using collectionView I'ld like UITableView will be of great use. Just use a UIViewController and place two UIViews in front and back and place a UITableView
in middle
Hope this helps