如何水平居中 UICollectionView 单元格?

我做了一些研究,但是我找不到任何关于如何在 UICollectionView 中水平居中单元格的代码示例。

而不是第一个细胞是这样的 X00,我希望它是这样的 0X0。有什么办法可以做到吗?

编辑:

想象我想要什么:

enter image description here

当 CollectionView 中只有一个元素时,我需要它看起来像版本 B。当我得到多个元素时,它应该像版本 A 一样,但是有更多的元素。

目前,它看起来像版本 A,当我只有1个元素,我不知道如何使它看起来像 B。

谢谢你的帮助!

132251 次浏览

我认为你需要中心单元格,所以我会喜欢 UITableView 将是非常有用的,而不是使用 CollectionView。只要使用一个 UIViewController,在前面和后面放置两个 UIView,在中间放置一个 UITableView 希望这个能帮上忙

我用 KTCenterFlowLayout做这个,效果很好。它是 UICollectionViewFlowLayout的一个自定义子类,可以随意定位细胞。(注意: 通过发布一些代码来解决这个问题并非易事,这就是为什么我要链接到 GitHub 项目!)

如果你的目的仅仅是为了中心对齐,那么使用图书馆就不是一个好主意。

更好的方法是在 CollectionViewLayout 函数中进行这个简单的计算。

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

目标 -C 版本的 Darshan Patel 的回答:

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

你可以试试我的解决方案,效果很好,

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

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)


}

斯威夫特

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)


}

别忘了加上协议

UICollectionViewDelegateFlowLayout

稍微修改一下@Safad Funy 的回答,这就是我在最新版的 Swift & iOS 中得到的效果。在本例中,我希望单元格的宽度是集合视图的三分之一。

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

试试这个

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

添加 cellWidth 而不是165.0

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


}

您可以使用这个扩展(Swift 4)。

如果你的 collectionViewlayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize,它可以使细胞居中。

它与任何细胞的大小和工作时完美的 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)
}
}

希望对你有帮助!

接受的答案是正确的答案,但如果你的 totalCellWidth小于 CollectionViewwidth,但只是为了防止这一点,你可以做如下。

if (leftInset > 0) {
return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
} else {
return UIEdgeInsetsMake(0, 10, 0, 10)
}

对于那些只想添加 填充物(上,左,下,右)的人:

添加协议 UICollectionViewDelegateFlowLayout

此示例显示了一个40的左右填充。

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 40, 0, 40)
}

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

这段代码应该在没有任何修改的情况下在 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
}
}

确保您的类符合 UICollectionViewDelegateFlowLayout协议

迅捷4.2 (水平和垂直)。这是粉红豹代码的小升级,非常感谢他!


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)


}
}

确保您的类符合 视图代表流布局协议

我最终采取了一种完全不同的方法,我认为这一点值得一提。

我在集合视图上设置了一个约束,使其在中间水平对齐。然后我设置另一个约束来指定宽度。我在 viewController 中创建了一个出口 用于宽度约束,用于保存集合视图。然后,当数据源发生更改并更新集合视图时,我将计算单元格的数量并进行(非常类似的)计算以重置宽度。

let newWidth = (items.count * cellWidth) + (items.count * cellSpacing)

然后将约束出口的 .constant值设置为计算结果,自动布局完成其余的工作。

这可能与“ UICollectionViewgenerateFlowLayout”冲突,但是对于我来说,创建左对齐的集合视图非常有效。如果没有委托,它似乎只有在单元格填充了大部分视图时才能工作。

流程布局的通用解决方案,如果页面小于宽度,则使其居中,如果页面多于宽度,则使其左对齐

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

快速版本(从对象转换)

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
}

Untitled-3.png

下面是 Swift 5的新版本,当单元格超过一行时也能正常工作:

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

请确保您的类 符合UICollectionViewDelegateFlowLayout协议。

我在项目中也有类似的情况,我通过参考 UPCarouselFlowLayout修正了它

我认为它支持 swift 5版本

Https://github.com/ink-spot/upcarouselflowlayout/blob/master/upcarouselflowlayout/upcarouselflowlayout.swift

中的代码实现

override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint

最简单的方法是在情节串连板或代码 layout.estimatedItemSize = CGSize.zero中将集合视图估计大小设置为无

如果每组只能容纳一个细胞,则 .flexible(0)leading:trailing:将使细胞水平居中:

item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
leading: .flexible(0), top: nil,
trailing: .flexible(0), bottom: nil
)

我在一个项目中用过这个代码。它使用部分的 insets 将 CollectionView 水平和垂直地集中在 .horizontal.vertical两个方向上。如果设置了该部分的间距和原始插入,则尊重该部分的间距。 在代理 UICollectionViewDelegateFlowLayout中使用的代码,这样我们就可以访问我们需要从 UIcollectionView中检索的所有属性,或者在故事板中为了重用而设置的所有属性。

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

希望它会帮助有人-它的中心部分,而不是内部的一节项目,为了更多,我们必须子类的 UICollectionViewFlowLayoutUICollectionViewLayout作为马赛克的例子,从苹果。

当数据更多时,上述答案有一个改进; 使用以下代码:

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

为了 Swift 5.2

我做了一些调整以满足我的要求

  1. 如果内容没有扩展到屏幕之外,则不要滚动
  2. 如果内容没有扩展到屏幕之外,则居中
  3. 如果内容扩展到屏幕之外,请确保从索引0开始,并允许从边缘缩进16(容易调整)时滚动
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)
}

通过在视图控制器中执行此操作来使用此选项

extension BaseViewController : UICollectionViewDelegateFlowLayout {

然后你就可以打电话了

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return centerItemsInCollectionView(cellWidth: 70, numberOfItems: 3, spaceBetweenCell: 25, collectionView: collectionView)
}

记住,如果你想为你的细胞设置自己的大小,你需要 在故事板中将 CollectionViewEstimatedSize 设置为无在情节串连板中设置您的单元格大小为自定义

然后你就可以打电话了

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.init(width:60, height: 60)
}

如果您正在努力让方法被调用,那么最好让所有的委托都被监听

extension BaseViewController : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

当然,不要忘了设定代表人数,仅仅延长代表人数是不够的。

override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
...

如果使用的是 Darshan Patel 的应答,还可以在 UICollectionViewFlowLayout子类中执行计算。

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

您必须在这里定义一个自定义的 UICollectionViewFlowLayout,如下所示。

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

然后向相关 viewController 的 viewDidLoad 或相关方法添加以下内容,

let layout = CenterAlignedCollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 200, height: 40)
self.collectionRateCustomer.collectionViewLayout = layout

别忘了继承 视图代表流布局

参考文献,Equales.comAlex Koshy 的回答

另一种方法是将空间除以要显示的单元格数,这样就可以模拟。StackView 的 FillEqually 模式

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

别忘了:

  • 将单元格内容视图中的视图设置为固定宽度,在其容器中居中,不带拖尾和前导约束
  • 删除 CollectionView 检查器中单元格和行的最小间距,将它们设置为0
  • 在上面的代码片段中提供一个高度,而不是现在的28

在多行中,这个方法对我很有效:

    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)


}

迅捷5 修改的 目前接受的答案,解决问题时,你的单元格不能适应屏幕宽度。

确保用您自己的自定义值替换 cellWidthcellSpacing

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
}

我也处理过同样的问题,作为一个解决方案,我从这个项目中复制了布局,并将它作为 UICollectionViewFlowLayout 应用到我的 UICollectionView 中。您不需要应用完整的库,只需要复制布局类。

Https://github.com/coeur/collectionviewcenteredflowlayout

通过 CollectionView 内容大小设置 insets。collectionView center and cell size according to text

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let font = FontNames.mediumFont12
let fontAttributes = [NSAttributedString.Key.font: font]
let text = "Your text"
let size = (text as NSString).size(withAttributes: fontAttributes)
self.setInsets(width: self.tagsCollectionView.contentSize.width)
return CGSize(width: size.width+30.0, height: self.tagsCollectionView.bounds.height)
}
func setInsets(width: CGFloat) {
let inset = self.tagsCollectionView.bounds.width - width
if inset <= 26.0 {
self.tagsCollectionView.contentInset = UIEdgeInsets(top: 0.0, left: 26.0, bottom: 0.0, right: 26.0)
} else {
self.tagsCollectionView.contentInset = UIEdgeInsets(top: 0.0, left: inset/2.0, bottom: 0.0, right: inset/2.0)
}
}

改变单元格宽度和内部填充

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth : CGFloat = 100
let paddingInside: CGFloat = 10
        

let numberOfCells = floor(collectionView.frame.size.width / cellWidth)
let edgeInsets = (collectionView.frame.size.width - (numberOfCells * cellWidth)) / 2 - paddingInside
        

        

return UIEdgeInsets(top: 0, left: edgeInsets, bottom: 0, right: edgeInsets)
}

并且不要忘记确认你的 vc 到 UICollectionViewgenerateFlowLayout 协议