如何水平居中 UICollectionView 单元格?
Posted
技术标签:
【中文标题】如何水平居中 UICollectionView 单元格?【英文标题】:How to center horizontally UICollectionView Cells? 【发布时间】:2015-12-14 12:57:23 【问题描述】:我已经做了一些研究,但我找不到任何关于如何在 UICollectionView 中水平居中单元格的代码示例。
第一个单元格不是像这样 X00,我希望它像这样 0X0。有没有办法做到这一点?
编辑:
可视化我想要的:
当 CollectionView 中只有一个元素时,我需要它看起来像版本 B。当我得到多个元素时,它应该与版本 A 类似,但元素更多。
目前,当我只有 1 个元素时,它看起来像版本 A,我想知道如何让它看起来像 B。
感谢您的帮助!
【问题讨论】:
让单元格适合集合视图的宽度,然后将集合视图本身居中放在其父级中不是更容易吗? 是的,至少有两种方法可以做到这一点,第一种(快速)是使整个屏幕的单元格宽度并将其子视图居中。第二(右)实现自定义集合视图布局 最终会有更多来自后端的单元格,填充整个宽度不是一个好主意 增加宽度足以设置在中心 How to center align the cells of a UICollectionView?的可能重复 【参考方案1】:如果您的目的只是为了中心对齐,那么使用库不是一个好主意。
你可以在你的 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)
【讨论】:
@DarshanPatel.Thanks 很多答案我实现了这个并且行应该在中心但是现在的问题是我不能滚动到第一个项目。当我尝试滚动到第一项时,它让我回到修改后的 UIEdgeInsets。你可以看到我的演示应用github.com/anirudha-music/CollectionViewCenter 在 Swift 3 中,新的方法签名是collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int)
。
如何获取cellWidth,是否可以通过cellForItemAt方法?我试过了,它返回 nil .. 我的单元格宽度会根据屏幕尺寸发生变化.. @DarshanPatel。
@DarshanPatel。您的回答有时会在较小的设备上产生负值。考虑对您的leftInset
值使用最大检查,如下所示:let leftInset = max(0.0, (self.collectionView.bounds.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2)
如果您没有继承 UICollectionViewController,请确保您的类符合 UICollectionViewDelegateFlowLayout 否则它将无法工作【参考方案2】:
斯威夫特 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)
斯威夫特 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)
斯威夫特 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)
别忘了添加协议
UICollectionViewDelegateFlowLayout
【讨论】:
约束可能是@ashForios 当有适合collectionView的单元格时,当单元格计数增加时,您的答案工作正常,单元格居中并且您无法滚动到第一个单元格或最后一个单元格。 @Vitalii 80 表示单元格宽度,10 表示单元格之间的间距,如果您阅读变量名称,您就会明白它的含义:P Swift 4.2 解决方案更简单,谢谢!只要确保将“80”设置为您的实际单元格对象宽度。 这对我不起作用,第一个单元格运行良好,但其他单元格在左侧显示太多。【参考方案3】:在 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)
添加你的 cellWidth 而不是 165.0
【讨论】:
这是最好的答案。用最简单的数学进去。适用于任意数量的行和列 这对我不起作用,第一个单元格运行良好,但左侧的其他单元格显示太多。【参考方案4】:我为此使用KTCenterFlowLayout,效果很好。它是UICollectionViewFlowLayout
的自定义子类,可以根据需要将单元格居中。 (注意:这不是通过发布一些代码来解决的小问题,这就是我链接到 GitHub 项目的原因!)
【讨论】:
无法在 IB 中使用。 This library 对我来说就像一个魅力。刚刚安装了 pod 并在 IB 中更改了 layout 的类!【参考方案5】:Darshan Patel's answer 的 Objective-C 版本:
- (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;
【讨论】:
谢谢。它在单行上工作正常。但在多行中创建问题。我无法在此处添加 url 以显示屏幕截图。 .但您可以在小网址中添加“yynoalzg”。你会明白的。黄金部分有4条记录。 4th应该在新行中。 .但是在这种方法之后,它会像那样显示......如果有任何想法,请告诉我。【参考方案6】:您可以使用此扩展程序 (Swift 4)。
如果collectionView
有layout.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)
希望对你有帮助!
【讨论】:
出现错误:线程 1:EXC_BAD_ACCESS (code=2, address=0x118ffde58)【参考方案7】:稍微修改@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)
【讨论】:
这对我特别有用,当只有一个单元格时。【参考方案8】:流程布局的一般解决方案,如果页面小于宽度,则将页面居中,如果有更多,则向左对齐
- (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 版本(从 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
【讨论】:
swift 代码的边界是什么?我收到使用未解决的标识符“边界”错误 嗨,在我的示例中,我在 UIView 中实现了该方法,该方法具有边界,如果您在其他地方实现它,请使用适当的边界【参考方案9】:Swift 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)
确保您的类符合 UICollectionViewDelegateFlowLayout 协议
【讨论】:
【参考方案10】:这是 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
协议。
【讨论】:
【参考方案11】:斯威夫特 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)
【讨论】:
【参考方案12】:对于只想添加填充(上、左、下、右)的人:
添加协议UICollectionViewDelegateFlowLayout
这个例子显示了一个 40 左右的内边距。
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
return UIEdgeInsetsMake(0, 40, 0, 40)
【讨论】:
【参考方案13】: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)
【讨论】:
【参考方案14】:如果你使用Darshan Patel's答案,你也可以在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)
【讨论】:
【参考方案15】:你可以试试我的解决方案,效果很好,
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)
【讨论】:
【参考方案16】:接受的答案是正确的答案,但如果您的totalCellWidth
小于CollectionView
的width
,但为了防止这种情况,您可以执行以下操作。
if (leftInset > 0)
return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
else
return UIEdgeInsetsMake(0, 10, 0, 10)
【讨论】:
【参考方案17】:即使在 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
协议
【讨论】:
【参考方案18】:如果每组只有一个单元格,leading:
和 trailing:
或 .flexible(0)
将使单元格水平居中:
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
leading: .flexible(0), top: nil,
trailing: .flexible(0), bottom: nil
)
【讨论】:
【参考方案19】:我最终在这里采取了完全不同的方法,我认为这值得一提。
我在我的集合视图上设置了一个约束,使其在中心水平对齐。然后我设置了另一个指定宽度的约束。我在 viewController 内部为宽度约束创建了一个出口,它包含集合视图。然后,当我的数据源更改并更新集合视图时,我会计算单元格的数量并进行(非常相似的)计算以重置宽度。
let newWidth = (items.count * cellWidth) + (items.count * cellSpacing)
然后我将约束出口的.constant
值设置为计算结果,其余的由自动布局完成。
这可能与 `UICollectionViewDelegateFlowLayout 冲突,但这非常适合我创建左对齐的集合视图。如果没有委托,它似乎只有在单元格填满大部分视图时才有效。
【讨论】:
【参考方案20】:我在项目中有类似的情况,我参考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
【讨论】:
【参考方案21】:最简单的方法是在故事板中或使用代码layout.estimatedItemSize = CGSize.zero
将集合视图估计大小设置为无
【讨论】:
【参考方案22】:我在一个项目中使用了该代码。它使用部分的插图在.horizontal
和.vertical
两个方向上水平和垂直居中collectionView。如果设置,则尊重节的间距和原始插图。
在委托 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)
希望它对某人有所帮助 - 它使部分居中而不是部分内的项目,更多我们必须将 UICollectionViewFlowLayout
或 UICollectionViewLayout
子类化为 Apple 的马赛克示例。
【讨论】:
【参考方案23】:当数据更多时,上述答案有一个改进; 使用以下代码:
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)
【讨论】:
【参考方案24】:您必须在此处定义一个自定义 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
别忘了继承UICollectionViewDelegateFlowLayout
参考,equaleyes.com 和 Alex Koshy's answer
【讨论】:
【参考方案25】:另一种方法是将您拥有的空间除以您要显示的单元格数量,这样您将模仿 stackViews 的 .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 在上面代码的 sn-p 中提供一个高度,而不是现在的 28【讨论】:
【参考方案26】:在多行中,这对我有用:
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)
【讨论】:
【参考方案27】:Swift 5 对 currently accepted answer 的修改,修复了单元格无法适应屏幕宽度时的问题。
确保将cellWidth
和cellSpacing
替换为您自己的自定义值。
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
【讨论】:
【参考方案28】:我已经处理了同样的问题,作为解决方案,我从这个项目中复制了布局并将其作为 UICollectionViewFlowLayout 应用到我的 UICollectionView。您不需要应用完整的库,只需复制布局类即可。
https://github.com/Coeur/CollectionViewCenteredFlowLayout
【讨论】:
【参考方案29】:对于 Swift 5.2
我做了一些调整以满足我的要求
-
如果内容没有超出屏幕,则不滚动
如果内容没有延伸到屏幕之外,则居中
如果内容确实延伸到屏幕之外,请确保从索引 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)
请记住,如果您想为您的单元格设置自己的大小,您需要在故事板中将 collectionView Estimated Size 设置为 none 并在故事板中将您的单元格大小设置为自定义强>
那你就可以打电话了
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
...
【讨论】:
【参考方案30】:我认为您需要将 cell 居中,所以我希望 UITableView 可以很好地使用而不是使用 collectionView。只需使用一个 UIViewController 并在前后放置两个 UIView 并在中间放置一个 UITableView
希望这会有所帮助
【讨论】:
我添加了一张图片,向您展示我到底需要什么,谢谢!以上是关于如何水平居中 UICollectionView 单元格?的主要内容,如果未能解决你的问题,请参考以下文章
Swift - 按单元对 UICollectionView 进行分页,同时保持单元水平居中