【问题标题】:UICollectionView Set number of columnsUICollectionView 设置列数
【发布时间】:2013-01-18 11:00:36
【问题描述】:

我刚开始学习 UICollectionViews。我想知道是否有人知道如何指定collectionview 中的列数。默认设置为 3(iPhone/纵向)。我查看了文档,似乎找不到简明的答案。

【问题讨论】:

  • 我有一个应用程序,我基本上将UICollectionView 用作网格,因为我知道我只想要 4 列。我通过使我的单元格具有一定的大小和它们之间的间距来做到这一点,因此无论方向如何,collectionview 都只显示 4 列。这与您需要的相似吗?
  • 还是更笼统的问题,比如能否动态设置列数?
  • 基本上我希望在纵向模式下有 7 列。所以我将列的宽度设为 46 像素(320 像素/46 像素 = 7)。我不知道它们是一个间距属性。

标签: ios objective-c xcode uicollectionview uicollectionviewcell


【解决方案1】:

在 Swift 5 和 iOS 12.3 中,您可以使用 4 个以下实现 来设置 UICollectionView 中每行的项目数,同时管理插入和大小更改(包括旋转) .


#1。继承UICollectionViewFlowLayout 并使用UICollectionViewFlowLayoutitemSize 属性

ColumnFlowLayout.swift:

import UIKit

class ColumnFlowLayout: UICollectionViewFlowLayout {

    let cellsPerRow: Int

    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 prepare() {
        super.prepare()

        guard let collectionView = collectionView else { return }
        let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        itemSize = CGSize(width: itemWidth, height: itemWidth)
    }

    override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
        let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
        context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
        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
        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 59
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = UIColor.orange
        return cell
    }

}


#2。使用UICollectionViewFlowLayoutitemSize方法

import UIKit

class CollectionViewController: UICollectionViewController {

    let margin: CGFloat = 10
    let cellsPerRow = 5

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let collectionView = collectionView, let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else { return }

        flowLayout.minimumInteritemSpacing = margin
        flowLayout.minimumLineSpacing = margin
        flowLayout.sectionInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)

        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func viewWillLayoutSubviews() {
        guard let collectionView = collectionView, let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
        let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + flowLayout.minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        flowLayout.itemSize =  CGSize(width: itemWidth, height: itemWidth)
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 59
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = UIColor.orange
        return cell
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionView?.collectionViewLayout.invalidateLayout()
        super.viewWillTransition(to: size, with: coordinator)
    }

}

#3。使用UICollectionViewDelegateFlowLayoutcollectionView(_:layout:sizeForItemAt:)方法

import UIKit

class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    let inset: CGFloat = 10
    let minimumLineSpacing: CGFloat = 10
    let minimumInteritemSpacing: CGFloat = 10
    let cellsPerRow = 5

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return minimumLineSpacing
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return minimumInteritemSpacing
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let marginsAndInsets = inset * 2 + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        return CGSize(width: itemWidth, height: itemWidth)
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 59
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = UIColor.orange
        return cell
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionView?.collectionViewLayout.invalidateLayout()
        super.viewWillTransition(to: size, with: coordinator)
    }

}

#4。继承UICollectionViewFlowLayout 并使用UICollectionViewFlowLayoutestimatedItemSize 属性

CollectionViewController.swift:

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "Lorem ipsum dolor sit amet, consectetur.",
        "Lorem ipsum dolor sit amet.",
        "Lorem ipsum dolor sit amet, consectetur.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing.",
        "Lorem ipsum.",
        "Lorem ipsum dolor sit amet.",
        "Lorem ipsum dolor sit.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.",
        "Lorem ipsum dolor sit amet, consectetur."
    ]

    let columnLayout = FlowLayout(
        cellsPerRow: 3,
        minimumInteritemSpacing: 10,
        minimumLineSpacing: 10,
        sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView?.collectionViewLayout = columnLayout
        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.register(Cell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
        cell.label.text = items[indexPath.row]
        return cell
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionView?.collectionViewLayout.invalidateLayout()
        super.viewWillTransition(to: size, with: coordinator)
    }

}

FlowLayout.swift:

import UIKit

class FlowLayout: UICollectionViewFlowLayout {

    let cellsPerRow: Int

    required init(cellsPerRow: Int = 1, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
        self.cellsPerRow = cellsPerRow

        super.init()

        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
        estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath) else { return nil }
        guard let collectionView = collectionView else { return layoutAttributes }

        let marginsAndInsets = collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + sectionInset.left + sectionInset.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        layoutAttributes.bounds.size.width = ((collectionView.bounds.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)

        return layoutAttributes
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let superLayoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
        guard scrollDirection == .vertical else { return superLayoutAttributes }

        let layoutAttributes = superLayoutAttributes.compactMap { layoutAttribute in
            return layoutAttribute.representedElementCategory == .cell ? layoutAttributesForItem(at: layoutAttribute.indexPath) : layoutAttribute
        }

        // (optional) Uncomment to top align cells that are on the same line
        /*
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            guard let max = attributes.max(by: { $0.size.height < $1.size.height }) else { continue }
            for attribute in attributes where attribute.size.height != max.size.height {
                attribute.frame.origin.y = max.frame.origin.y
            }
        }
         */

        // (optional) Uncomment to bottom align cells that are on the same line
        /*
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            guard let max = attributes.max(by: { $0.size.height < $1.size.height }) else { continue }
            for attribute in attributes where attribute.size.height != max.size.height {
                attribute.frame.origin.y += max.frame.maxY - attribute.frame.maxY
            }
        }
         */

        return layoutAttributes
    }

}

Cell.swift:

import UIKit

class Cell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        layoutIfNeeded()
        label.preferredMaxLayoutWidth = label.bounds.size.width
        layoutAttributes.bounds.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        return layoutAttributes
    }

    // Alternative implementation
    /*
    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        label.preferredMaxLayoutWidth = layoutAttributes.size.width - contentView.layoutMargins.left - contentView.layoutMargins.right
        layoutAttributes.bounds.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        return layoutAttributes
    }
    */

}

【讨论】:

  • 使用“viewWillTransition(to size ...”的解决方案不能正确处理设备旋转。由于它是在设置框架之前调用的,所以使用collectionView的旧尺寸来计算单元格尺寸。(其他方案还没试过)
  • 我使用了解决方案#1,你是我的新英雄。
  • 我使用了您的解决方案 #1。太棒了。
  • 伟大而完整的答案,实施#2
  • 带有清晰示例的精彩答案。我也很欣赏它如何为您指出不同的选择。它值得更多的支持。
【解决方案2】:

CollectionView 非常强大,但它们是有代价的。很多,还有很多选择。正如 omz 所说:

您可以通过多种方式更改列数

我建议实现&lt;UICollectionViewDelegateFlowLayout&gt; 协议,让您可以访问以下方法,您可以更好地控制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

【讨论】:

  • 这适用于所有 iOS 设备吗?是否应该为所有设备分别处理 collectionView:layout:sizeForItemAtIndexPath:
  • @Sharath,当然这适用于所有事情。您只需要以适用于所有设备的方式进行数学计算;)。自适应设计。
  • 哇太棒了,但真实的例子?在Android中我们可以new GridLayoutManager(mContext, 4))(4列),非常简单,或者直接使用LinearLayoutManager(当然只有1列)
【解决方案3】:

我在UICollectionViewController 上实现了UICollectionViewDelegateFlowLayout,并覆盖了负责确定单元格大小的方法。然后我将屏幕宽度与我的列要求分开。例如,我想在每个屏幕尺寸上有 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;
}

【讨论】:

  • 简单多了,感谢您的理智和更少的代码
  • 您应该确保将项目间距设置为 0,以避免每行出现 n-1 个高间距项目。
【解决方案4】:

扩展菜鸟的答案:

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 成员变量,并且所有单元格都是正方形且大小相同。正如 jhilgert00 的回答中所述,我们还必须对方向变化做出反应,但现在使用 viewWillTransitionToSize 因为 willRotateToInterfaceOrientation 已被贬值。

【讨论】:

  • let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(numberOfItemsPerRow)) - 你应该删除转换为 Int,因为在某些情况下它会在单元格之间产生小空间
【解决方案5】:

这是 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”更改为您想要的列数。

【讨论】:

  • 当 insets 为 (1.0,1.0,1.0,1.0) 时,每个解决方案有什么问题,在这种情况下布局变为 1 列!
  • @fraxool 很好的答案,但如果我翻转设备,布局会重置...有什么解决办法吗?
  • 我找到了最好的解决方案。所以你可以毫无疑问地尝试。谢谢回答
【解决方案6】:

更新到 Swift 5+ iOS 13

Collectionview Estimate Size 必须为none

声明单元格的边距

let margin: CGFloat = 10

在 viewDidLoad 中配置minimumInteritemSpacingminimumLineSpacingsectionInset

 guard let collectionView = docsColl, 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)

UICollectionViewDataSource 方法sizeForItemAt

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    let noOfCellsInRow = 2   //number of column you want
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let totalSpace = flowLayout.sectionInset.left
        + flowLayout.sectionInset.right
        + (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1))

    let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(noOfCellsInRow))
    return CGSize(width: size, height: size)
}

【讨论】:

    【解决方案7】:

    如果你懒惰使用委托。

    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:也应该在轮换后调用

    【讨论】:

    • 它也可以在 Swift 4.0 中使用...在更改最后两行之后: let width = floor((frame.width - insetSpace - innerSpace) / itemsInRow) layout.itemSize = CGSize(width: width,高度:宽度)
    【解决方案8】:

    更新到 Swift 3:

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

    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 了!

    【讨论】:

    • 采用此解决方案的问题在于您的静态高度和宽度
    • @rd_Custom 布局意味着您可以做任何您想做的事情!我的代码只是展示了最简单的情况......
    【解决方案9】:

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

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

    【讨论】:

      【解决方案10】:

      斯威夫特 3.0。适用于水平和垂直滚动方向和可变间距

      指定列数

      let numberOfColumns: CGFloat = 3
      

      配置flowLayout 渲染指定的numberOfColumns

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

      【讨论】:

      • 所有这些长答案无处不在,这适用于 5 行代码:)
      • 很好的尝试。但是,如果您在 collectionViewLayout 上使用 sectionInset,这将不起作用。要解决此问题,(1) 通过获取单元格宽度并从中减去 flowLayout.sectionInset.left 和 flowLayout.sectionInset.right 来计算可用宽度。 (2) 在计算 cellWidth 的地方,将“view.frame.width”替换为在 (1) 处计算的值。另外,请记住,视图的框架只有在加载后才会最终确定,并且可以更改,因此您必须根据需要重新构建布局。
      【解决方案11】:

      这一切都与您要绘制的布局有关。您可以创建从 UICollectionViewFlowLayout 继承的自定义类。目前没有任何直接的方法来设置列。如果你想实现这种功能,你需要手动完成。您需要在自定义流布局类中处理它。

      现在会出现一个问题,您将如何做到这一点?如果您不想打扰单元格框架,您可以调整

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

      另一种方法是为您提供自己的单元格位置。通过覆盖以下两种方法,它们将在您的布局形成期间被调用。

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

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

      【讨论】:

        【解决方案12】:

        此答案适用于 swift 3.0 -->

        首先符合协议:

         UICollectionViewDelegateFlowLayout
        

        然后添加这些方法:

            //this method is for the size of items      
            func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
                let width = collectionView.frame.width/3
                let height : CGFloat = 160.0
                return CGSize(width: width, height: height)
            }
            //these methods are to configure the spacing between items
        
            func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
                return UIEdgeInsetsMake(0,0,0,0)
            }
        
            func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
                return 0
            }
        
            func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
                return 0
            }
        

        【讨论】:

          【解决方案13】:

          首先添加 UICollectionViewDelegateFlowLayout 作为协议。

          然后:

          func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
          {
                  var columnCount = 3
                  let width  = (view.frame.width - 20) / columnCount
                  return CGSize(width: width, height: width)
          }
          

          【讨论】:

            【解决方案14】:

            我只是想附加到 Imanou Petit 的答案 #2。为了确保无论屏幕宽度如何,边距都是准确的,我使用具有所需边距的迭代求解器,并将列数作为输入。我还添加了一个方向标志,用于将它们的最终边距与目标进行比较。

            迭代求解器如下所示,返回单元格宽度和边距。

            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 值应用到流布局中:

            extension MyCollectionController : UICollectionViewDelegateFlowLayout
            

            { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 返回CGSize(宽度:cellWidth,高度: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
            }
            

            }

            希望这会有所帮助。可能有一种更简单的方法可以确保边距准确,但这是一种方法。此外,此代码尚未针对允许旋转集合视图的设备进行测试。

            谢谢,

            【讨论】:

              【解决方案15】:

              完美的解决方案是 使用 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;}
              

              【讨论】:

                【解决方案16】:

                我做了一个集合布局。

                要使分隔符可见,请将集合视图的背景颜色设置为灰色。 每节一行。

                用途:

                let layout = GridCollectionViewLayout()
                layout.cellHeight = 50 // if not set, cellHeight = Collection.height/numberOfSections
                layout.cellWidth = 50  // if not set, cellWidth = Collection.width/numberOfItems(inSection)
                collectionView.collectionViewLayout = layout
                

                布局:

                import UIKit
                
                class GridCollectionViewLayout: UICollectionViewLayout {
                
                
                var cellWidth : CGFloat = 0
                var cellHeight : CGFloat = 0
                var seperator: CGFloat = 1
                
                private var cache = [UICollectionViewLayoutAttributes]()
                
                
                
                override func prepare() {
                
                    guard let collectionView = self.collectionView else {
                        return
                    }
                
                    self.cache.removeAll()
                
                
                
                        let numberOfSections = collectionView.numberOfSections
                
                        if cellHeight <= 0
                        {
                            cellHeight = (collectionView.bounds.height - seperator*CGFloat(numberOfSections-1))/CGFloat(numberOfSections)
                        }
                
                        for section in 0..<collectionView.numberOfSections {
                
                            let numberOfItems = collectionView.numberOfItems(inSection: section)
                
                            let cellWidth2 : CGFloat
                            if cellWidth <= 0
                            {
                                cellWidth2 = (collectionView.bounds.width - seperator*CGFloat(numberOfItems-1))/CGFloat(numberOfItems)
                            }
                            else
                            {
                                cellWidth2 = cellWidth
                            }
                
                
                            for row in 0..<numberOfItems {
                
                
                                let indexPath = NSIndexPath(row: row, section: section)
                
                                let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
                                attributes.frame = CGRect(x: (cellWidth2+seperator)*CGFloat(row),
                                                          y: (cellHeight+seperator)*CGFloat(section),
                                                          width: cellWidth2,
                                                          height: cellHeight)
                
                                //row_temp.append(attributes)
                
                                self.cache.append(attributes)
                            }
                            //self.itemAttributes.append(row_temp)
                        }
                
                }
                
                override var collectionViewContentSize: CGSize {
                
                    guard let collectionView = collectionView else
                    {
                        return CGSize.zero
                    }
                
                    if (collectionView.numberOfSections <= 0)
                    {
                        return collectionView.bounds.size
                    }
                
                    let width:CGFloat
                    if cellWidth <= 0
                    {
                        width = collectionView.bounds.width
                    }
                    else
                    {
                        width = cellWidth*CGFloat(collectionView.numberOfItems(inSection: 0))
                    }
                
                    let numberOfSections = CGFloat(collectionView.numberOfSections)
                    var height:CGFloat = 0
                    height += numberOfSections * cellHeight
                    height += (numberOfSections - 1) * seperator
                
                
                
                    return CGSize(width: width, height: height)
                }
                
                override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
                
                    var layoutAttributes = [UICollectionViewLayoutAttributes]()
                
                    for attributes in cache {
                        if attributes.frame.intersects(rect) {
                            layoutAttributes.append(attributes)
                        }
                    }
                    return layoutAttributes
                }
                
                override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
                    return cache[indexPath.item]
                }
                }
                

                【讨论】:

                  【解决方案17】:

                  试试这个,它非常适合我,

                  按照以下步骤操作:

                  1.在 .h 文件中定义值。

                       #define kNoOfColumsForCollection 3
                       #define kNoOfRowsForCollection 4
                       #define kcellSpace 5
                       #define kCollectionViewCellWidth (self.view.frame.size.width - kcellSpace*kNoOfColumsForCollection)/kNoOfColumsForCollection
                       #define kCollectionViewCellHieght (self.view.frame.size.height-40- kcellSpace*kNoOfRowsForCollection)/kNoOfRowsForCollection
                  

                    #define kNoOfColumsForCollection 3
                    #define kCollectionViewCellWidthHieght (self.view.frame.size.width - 6*kNoOfColumsForCollection)/kNoOfColumsForCollection
                  

                  2.在collection View Layout数据源方法中添加代码如下,

                   #pragma mark Collection View Layout data source methods
                  // collection view with autolayout
                  
                  - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
                  {
                  return 4;
                  }
                  
                  - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
                   {
                   return 1;
                  }
                  
                  - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
                  {
                  return UIEdgeInsetsMake(4, 4, 4, 4);
                  }
                  - (CGSize)collectionView:(UICollectionView *)collectionView
                                layout:(UICollectionViewLayout *)collectionViewLayout
                     sizeForItemAtIndexPath:(NSIndexPath *)indexPath
                  {
                   return 
                   CGSizeMake(kCollectionViewCellWidth,kCollectionViewCellHieght);
                   // CGSizeMake (kCollectionViewCellWidthHieght,kCollectionViewCellWidthHieght);
                  }
                  

                  希望这对某些人有所帮助。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-09-21
                    • 2019-04-06
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多