UICollectionView设置列数

我刚开始学习UICollectionViews。 我想知道如果有人知道如何指定一个collectionview中的列数。 默认设置为3(iPhone /人像)。 我已经看了文档,似乎无法find一个简洁的答案。

CollectionViewsfunction非常强大,而且价格昂贵。 很多,和很多的select。 正如omz所说:

有多种方法可以更改列数

我build议实现<UICollectionViewDelegateFlowLayout>协议,让您访问以下方法,您可以更好地控制您的UICollectionView的布局,而不需要子类化:

  • collectionView:layout:insetForSectionAtIndex:
  • collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
  • collectionView:layout:minimumLineSpacingForSectionAtIndex:
  • collectionView:layout:referenceSizeForFooterInSection:
  • collectionView:layout:referenceSizeForHeaderInSection:
  • collectionView:layout:sizeForItemAtIndexPath:

另外,实现下面的方法将迫使你的UICollectionView更新它的方向变化的布局:(比如你想重新调整单元格的大小,使它们伸展)

 -(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{ [self.myCollectionView.collectionViewLayout invalidateLayout]; } 

另外,这里有2个关于UICollectionViews非常好的教程:

http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12

http://skeuo.com/uicollectionview-custom-layout-tutorial

我在我的UICollectionViewDelegateFlowLayout上实现了UICollectionViewDelegateFlowLayout ,并覆盖了负责确定Cell大小的方法。 然后我把屏幕宽度和我的列要求划分。 例如,我想在每个屏幕大小上有3列。 所以这里是我的代码看起来像 –

 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGRect screenRect = [[UIScreen mainScreen] bounds]; CGFloat screenWidth = screenRect.size.width; float cellWidth = screenWidth / 3.0; //Replace the divisor with the column count requirement. Make sure to have it in float. CGSize size = CGSizeMake(cellWidth, cellWidth); return size; } 

扩大noob的答案:

 func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout let totalSpace = flowLayout.sectionInset.left + flowLayout.sectionInset.right + (flowLayout.minimumInteritemSpacing * CGFloat(numberOfItemsPerRow - 1)) let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(numberOfItemsPerRow)) return CGSize(width: size, height: size) } 

这允许单元之间的任何间隔。 它假设一个名为numberOfItemsPerRowInt成员variables,并且所有单元格都是正方形且大小相同。 正如在jhilgert00的答案中指出的,我们还必须对方向更改做出反应,但是现在通过使用viewWillTransitionToSize因为willRotateToInterfaceOrientation已折旧。

使用Swift 3,您可以使用以下三个实现中的一个来设置UICollectionView每行的项目数量。


#1。 使用UICollectionViewFlowLayout itemSize属性和UICollectionViewLayout invalidationContext(forBoundsChange:)方法

ColumnFlowLayout.swift:

 import UIKit class ColumnFlowLayout: UICollectionViewFlowLayout { let cellsPerRow: Int override var itemSize: CGSize { get { guard let collectionView = collectionView else { return super.itemSize } let marginsAndInsets = sectionInset.left + sectionInset.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1) let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down) return CGSize(width: itemWidth, height: itemWidth) } set { super.itemSize = newValue } } init(cellsPerRow: Int, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) { self.cellsPerRow = cellsPerRow super.init() self.minimumInteritemSpacing = minimumInteritemSpacing self.minimumLineSpacing = minimumLineSpacing self.sectionInset = sectionInset } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext context.invalidateFlowLayoutDelegateMetrics = newBounds != collectionView?.bounds return context } } 

CollectionViewController.swift:

 import UIKit class CollectionViewController: UICollectionViewController { let columnLayout = ColumnFlowLayout( cellsPerRow: 5, minimumInteritemSpacing: 10, minimumLineSpacing: 10, sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) ) override func viewDidLoad() { super.viewDidLoad() collectionView?.collectionViewLayout = columnLayout } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 29 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) } } 

#2。 使用UICollectionViewDelegateFlowLayout collectionView(_:layout:sizeForItemAt:)UIContentContainer协议viewWillTransition(to:with:)方法

 import UIKit class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { let margin: CGFloat = 10 let cellsPerRow = 5 override func viewDidLoad() { super.viewDidLoad() guard let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else { return } flowLayout.minimumInteritemSpacing = margin flowLayout.minimumLineSpacing = margin flowLayout.sectionInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin) } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 29 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + flowLayout.minimumInteritemSpacing * CGFloat(cellsPerRow - 1) let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down) return CGSize(width: itemWidth, height: itemWidth) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { collectionView?.collectionViewLayout.invalidateLayout() super.viewWillTransition(to: size, with: coordinator) } } 

#3。 使用UICollectionViewDelegateFlowLayout collectionView(_:layout:sizeForItemAt:)UICollectionViewFlowLayout estimatedItemSize属性

请注意,这段代码只适用于Springs和Struts。

 import UIKit class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { let margin: CGFloat = 10 let cellsPerRow = 5 override func viewDidLoad() { super.viewDidLoad() guard let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else { return } flowLayout.minimumInteritemSpacing = margin flowLayout.minimumLineSpacing = margin flowLayout.sectionInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin) flowLayout.estimatedItemSize = flowLayout.itemSize // CGSize(width: 50, height: 50) } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 29 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + flowLayout.minimumInteritemSpacing * CGFloat(cellsPerRow - 1) let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down) return CGSize(width: itemWidth, height: itemWidth) } } 

你可以在这个Github仓库中find这些例子和捆绑在Xcode项目中的另一个例子: CollectionView-Columns-Examples 。

更新到Swift 3:

而不是stream布局,我更喜欢使用自定义布局的特定列号和行号。 因为:

  1. 如果列号很大,可以水平拖动。
  2. 由于使用列和行,因此在逻辑上更为可接受。

正常单元格和标题单元格:(将UILabel作为IBOutlet添加到您的xib):

 class CollectionViewCell: UICollectionViewCell { @IBOutlet weak var label: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code self.backgroundColor = UIColor.black label.textColor = UIColor.white } } class CollectionViewHeadCell: UICollectionViewCell { @IBOutlet weak var label: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code self.backgroundColor = UIColor.darkGray label.textColor = UIColor.white } } 

自定义布局:

 let cellHeight: CGFloat = 100 let cellWidth: CGFloat = 100 class CustomCollectionViewLayout: UICollectionViewLayout { private var numberOfColumns: Int! private var numberOfRows: Int! // It is two dimension array of itemAttributes private var itemAttributes = [[UICollectionViewLayoutAttributes]]() // It is one dimension of itemAttributes private var cache = [UICollectionViewLayoutAttributes]() override func prepare() { if self.cache.isEmpty { self.numberOfColumns = self.collectionView?.numberOfItems(inSection: 0) self.numberOfRows = self.collectionView?.numberOfSections // Dynamically change cellWidth if total cell width is smaller than whole bounds /* if (self.collectionView?.bounds.size.width)!/CGFloat(self.numberOfColumns) > cellWidth { self.cellWidth = (self.collectionView?.bounds.size.width)!/CGFloat(self.numberOfColumns) } */ for row in 0..<self.numberOfRows { var row_temp = [UICollectionViewLayoutAttributes]() for column in 0..<self.numberOfColumns { let indexPath = NSIndexPath(item: column, section: row) let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath) attributes.frame = CGRect(x: cellWidth*CGFloat(column), y: cellHeight*CGFloat(row), width: cellWidth, height: cellHeight) row_temp.append(attributes) self.cache.append(attributes) } self.itemAttributes.append(row_temp) } } } override var collectionViewContentSize: CGSize { return CGSize(width: CGFloat(self.numberOfColumns)*cellWidth, height: CGFloat(self.numberOfRows)*cellHeight) } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var layoutAttributes = [UICollectionViewLayoutAttributes]() for attributes in cache { if attributes.frame.intersects(rect) { layoutAttributes.append(attributes) } } return layoutAttributes } } 

的CollectionView:

 let CellIdentifier = "CellIdentifier" let HeadCellIdentifier = "HeadCellIdentifier" class CollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource { init() { let layout = CustomCollectionViewLayout() super.init(frame: CGRect.zero, collectionViewLayout: layout) self.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: CellIdentifier) self.register(UINib(nibName: "CollectionViewHeadCell", bundle: nil), forCellWithReuseIdentifier: HeadCellIdentifier) self.isDirectionalLockEnabled = true self.dataSource = self self.delegate = self } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func updateCollectionView() { DispatchQueue.main.async { self.reloadData() } } // MARK: CollectionView datasource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 20 } func numberOfSections(in collectionView: UICollectionView) -> Int { return 20 } override func numberOfItems(inSection section: Int) -> Int { return 20 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let column = (indexPath as NSIndexPath).row let row = (indexPath as NSIndexPath).section if column == 0 { let cell : CollectionViewHeadCell = collectionView.dequeueReusableCell(withReuseIdentifier: HeadCellIdentifier, for: indexPath) as! CollectionViewHeadCell cell.label.text = "\(row)" return cell } else if row == 0 { let cell : CollectionViewHeadCell = collectionView.dequeueReusableCell(withReuseIdentifier: HeadCellIdentifier, for: indexPath) as! CollectionViewHeadCell cell.label.text = "\(column)" return cell } else { let cell : CollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIdentifier, for: indexPath) as! CollectionViewCell cell.label.text = String(format: "%d", arguments: [indexPath.section*indexPath.row]) return cell } } // MARK: CollectionView delegate func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let column = (indexPath as NSIndexPath).row let row = (indexPath as NSIndexPath).section print("\(column) \(row)") } } 

使用ViewController中的CollectionView:

 class ViewController: UIViewController { let collectionView = CollectionView() override func viewDidLoad() { collectionView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(collectionView) self.view.backgroundColor = UIColor.red self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[collectionView]|", options: [], metrics: nil, views: ["collectionView": collectionView])) self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[collectionView]|", options: [], metrics: nil, views: ["collectionView": collectionView])) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) collectionView.updateCollectionView() } } 

最后你可以有花哨的CollectionView!

在这里输入图像说明

因为UICollectionView非常灵活,您可以通过多种方式更改列的数量,具体取决于您使用的布局types。

UICollectionViewFlowLayout (这可能是你正在使用的)不直接指定数量的列(因为它取决于视图的大小/方向)。 最简单的方法是设置itemSize属性和/或minimumInteritemSpacing / minimumLineSpacing

这里是Swift 3的工作代码,有两列布局:

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let nbCol = 2 let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout let totalSpace = flowLayout.sectionInset.left + flowLayout.sectionInset.right + (flowLayout.minimumInteritemSpacing * CGFloat(nbCol - 1)) let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(nbCol)) return CGSize(width: size, height: size) } 

随意将“nbCol”更改为所需的列数。

如果你懒惰使用委托。

 extension UICollectionView { func setItemsInRow(items: Int) { if let layout = self.collectionViewLayout as? UICollectionViewFlowLayout { let contentInset = self.contentInset let itemsInRow: CGFloat = CGFloat(items); let innerSpace = layout.minimumInteritemSpacing * (itemsInRow - 1.0) let insetSpace = contentInset.left + contentInset.right + layout.sectionInset.left + layout.sectionInset.right let width = floor((CGRectGetWidth(frame) - insetSpace - innerSpace) / itemsInRow); layout.itemSize = CGSizeMake(width, width) } } } 

PS:也应该在旋转之后调用

它的所有关于您想要绘制的布局。 您可以创build从UICollectionViewFlowLayoutinheritance的自定义类。 目前没有任何直接设置列的方法。 如果你想实现这种function,你需要手动完成。 您需要在自定义stream布局类中处理它。

现在问题会出现,你会怎么做? 如果你不想打扰细胞框架,你可以调整

  collectionView:layout:minimumInteritemSpacingForSectionAtIndex: collectionView:layout:minimumLineSpacingForSectionAtIndex: 

另一种方法是提供你自己的细胞位置。 通过覆盖以下两种方法,这将在您的布局形成期间被调用。

  - (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path 

UICollectionViewLayoutAttributes是处理单元位置,框架,Zindex等的类

Swift 3.0。 适用于水平和垂直滚动方向和可变间距

指定列数

 let numberOfColumns: CGFloat = 3 

configurationflowLayout来呈现指定的numberOfColumns

 if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout { let horizontalSpacing = flowLayout.scrollDirection == .vertical ? flowLayout.minimumInteritemSpacing : flowLayout.minimumLineSpacing let cellWidth = (view.frame.width - max(0, numberOfColumns - 1)*horizontalSpacing)/numberOfColumns flowLayout.itemSize = CGSize(width: cellWidth, height: cellWidth) } 

完美的解决scheme是使用UICollectionViewDelegateFlowLayout,但你可以很容易地计算单元格的宽度,并根据你想要的列数

棘手的是使没有分数的宽度

 (UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat screenWidth = self.view.frame.size.width; CGFloat marginWidth = (screenWidth - collectionView.frame.size.width); CGFloat cellWith = (collectionView.frame.size.width - marginWidth )/3; cellWith= floorf(cellWith); CGSize retval = CGSizeMake(cellWith,cellWith); return retval;} 

我只是想追加Imanou Petit的答案#2。 为了确保边距精确而不考虑屏幕宽度,我使用具有所需边距的迭代解算器,并将列数作为input。 我还添加了一个方向标志,将他们的最终利润与他们的目标进行比较。

迭代求解器如下所示,并返回cellWidth和margin。

 private func iterativeCellSpacing(targetMargins : CGFloat, cellsPerRow : Int, isMinTarget : Bool) -> (CGFloat, CGFloat) { var w : CGFloat = 0 var m : CGFloat = targetMargins let cols : CGFloat = CGFloat(cellsPerRow) let numMargins : CGFloat = cols + 1.0 let screenWidth : CGFloat = collectionView!.bounds.size.width var delta = CGFloat.greatestFiniteMagnitude while abs(delta) > 0.001 { let totalMarginSpacing = numMargins * m let totalCellSpacing = screenWidth - totalMarginSpacing if (isMinTarget) { w = floor(totalCellSpacing / cols) m = ceil((screenWidth - cols * w) / numMargins) } else { w = ceil(totalCellSpacing / cols) m = floor((screenWidth - cols * w) / numMargins) } delta = screenWidth - w * CGFloat(cellsPerRow) - m * numMargins } return (w, m) } 

我这样称呼它:

 fileprivate var margin: CGFloat = 20 fileprivate var cellWidth : CGFloat = 80 fileprivate let cellsPerRow = 4 override func viewDidLoad() { super.viewDidLoad() (cellWidth, margin) = iterativeCellSpacing(targetMargins: margin, cellsPerRow: 4, isMinTarget: true) ... } 

然后,我将cellWidth和margin值应用到stream布局,如下所示:

 extension MyCollectionController : UICollectionViewDelegateFlowLayout 

{func collectionView(_ collectionView:UICollectionView,layout collectionViewViewLayout:UICollectionViewLayout,sizeForItemAt indexPath:IndexPath) – > CGSize {return CGSize(width:cellWidth,height:cellWidth)}

 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { collectionView?.collectionViewLayout.invalidateLayout() super.viewWillTransition(to: size, with: coordinator) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return UIEdgeInsetsMake(margin, margin, margin, margin) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return margin } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return margin } 

}

希望这可以帮助。 可能有更简单的方法来确保利润率是准确的,但这是一种方法。 而且,这个代码还没有经过testing,允许旋转collectionviews的设备。

谢谢,