了解 UICollectionViewFlowLayout 大小和间距

问题描述 投票:0回答:2

我正在使用

UICollectionView
处理
UICollectionViewFlowLayout
,并且在理解项目大小间距方面存在一些困难。我知道有几种方法可以调整大小和间距(使用委托方法、覆盖 FlowLayout 等)。 然而,如果不首先理解这些值背后的逻辑,就很难正确地调整它们。

以下结果已创建默认

UICollectionViewController
,其中默认
UICollectionViewCell
没有任何子类。仅进行了以下设置:

  • 在 IB 中将像元大小指定为 200 x 200
  • 将标签放置在单元格内并垂直和水平居中

代码:

private let reuseIdentifier = "Cell"

class MyViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var layout: UICollectionViewFlowLayout {
            let layout = UICollectionViewFlowLayout()
            layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
            layout.minimumLineSpacing = 0
            layout.minimumInteritemSpacing = 5
            layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 0, right: 20)
            layout.sectionInsetReference = .fromContentInset
            return layout
        }
        
        collectionView.collectionViewLayout = layout
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2
    }


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

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

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

layout.minimumInteritemSpacing = 5
使用不同的值会产生我不明白的结果:

  • 为什么项目的尺寸为 50 x 50px?我知道可以使用
    ...sizeForItemAt
    来指定显式尺寸。不过,在 IB 中设置大小应该也可以,不是吗?为什么 200 x 200px 的 IB 尺寸被忽略?为什么是 50x50,这是默认大小还是在哪里指定的?
    @Larme 的答案已解决
  • 我知道
    minimumInteritemSpacing
    没有设置显式间距,而只是设置 最小。然而,这个值是如何计算的呢?
  • 为什么
    7.5px
    的间距值为 5?为什么结果值 10-25 的结果都相同,间距为
    26.5px

那么:这里的尺寸和间距到底是如何计算的?

ios uicollectionview uicollectionviewlayout uicollectionviewflowlayout
2个回答
1
投票

您有多个问题(不太推荐),我会尽我所能回答。

为什么项目的尺寸为 50 x 50px?我知道可以使用 ...sizeForItemAt 来指定显式尺寸。不过,在 IB 中设置大小应该也可以,不是吗?为什么 200 x 200px 的 IB 尺寸被忽略?为什么是 50x50,这是默认尺寸还是在哪里指定的?

你正在做:

collectionView.collectionViewLayout = layout

新创建布局的位置。
您没有使用 InterfaceBuilder 中以前的设置,而是通过代码覆盖它们。
而您为

layout
创建的
itemSize
尚未设置。从doc来看,如果没有设置,它是50x50。

为什么这些值的项目向左对齐,并在值为 30 时均匀分布在整个宽度上?

你确定吗?默认布局(意思是,不是从

UICollectionViewFlowLayout
继承)将表现得像段落样式。 我将采用水平布局(相同的逻辑可以应用于垂直布局,但与文本段落的类比会很奇怪):

如果您有多行文本,第一行将占用尽可能多的宽度,但最后一行不会,保持“左”对齐。

对于间距计算,根据

minimumInterItemSpacing
的文档:

对于垂直滚动网格,该值表示同一行中项目之间的最小间距。对于水平滚动网格,该值表示同一列中项目之间的最小间距。 此间距用于计算一行中可以容纳多少个项目,但在确定项目数量后,实际间距可能会向上调整。

但是,我想知道,如果您覆盖

viewDidLayoutSubview()
,并调用
collectionView.collectionViewLayout?.invalidateLayout(); collectionView.collectionViewLayout?.prepareLayout()
,会发生什么。


0
投票

正如您从 Larme 中了解到的那样,您的 Storyboard 布局将被忽略,因为您创建了一个新布局。

当集合视图布置其单元格时,它会说:

  • .itemSize
    吗?
  • 不,询问单元格的大小
  • cell 返回
    .zero
    的大小(您的单元格没有影响其大小的约束)
  • 所以,使用
    .estimatedItemSize

在您的情况下,您设置

layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
这意味着 “使用默认值
(50, 50)

“那么:这里的尺寸和间距到底是如何计算的?”

好吧,让我们看看更多的细胞。

我们将使用 6 个部分...

  • 0 和 1 将有
    .minimumInteritemSpacing = 5.0
  • 2 和 3 将有
    .minimumInteritemSpacing = 10.0
  • 4 和 5 将有
    .minimumInteritemSpacing = 30.0

我们将在每个部分交替使用 3 到 7 个项目。看起来像这样:

答案现在非常明显 - 集合视图根据单元格宽度加上 minSpacing 计算将容纳多少个 total 单元格(而不是该部分中有多少个单元格) - 然后增加“完美配合”的间距。 ”

因此,假设集合视图框架宽度为

320.0
并且每侧的部分插入为
20.0
,我们可以计算调整后的项目间距:

最小项目间距 = 5.0

// CellWidth + MinSpacing
50.0 + 5.0 = 55.0

// collViewWidth - sectionInsets
320.0 - (20.0 + 20.0) = 280.0

// spacing is only BETWEEN cells, not at end, so
//  add spacing
280.0 + 5.0 = 285.0

// how many cells will fit?
285.0 / 55.0 = 5.1818181818181817

// can't have a "partial-cell" so 
floor(285.0 / 55.0) = 5.0
numCells = 5

// width of 5 cells
50.0 * numCells = 250.0

// "extra" space
280.0 - 250.0 = 30.0

// with 5 cells fitting, we have 4 "spaces" (numCells - 1)
30.0 / 4.0 = 7.5

actual spacing = 7.5

最小项目间距 = 10.0

// CellWidth + MinSpacing
50.0 + 10.0 = 60.0

// collViewWidth - sectionInsets
320.0 - (20.0 + 20.0) = 280.0

// spacing is only BETWEEN cells, not at end, so
//  add spacing
280.0 + 10.0 = 290.0

// how many cells will fit?
290.0 / 60.0 = 4.833333333333333

// can't have a "partial-cell" so 
floor(290.0 / 60.0) = 4.0
numCells = 4

// width of 4 cells
50.0 * 4.0 = 200.0

// "extra" space
280.0 - 200.0 = 80.0

// with 4 cells fitting, we have 3 "spaces"
80.0 / 3.0 = 26.666666 (rounded to 26.5 on @2x device scale)

actual spacing = 26.5

最小项目间距 = 30.0

// CellWidth + MinSpacing
50.0 + 30.0 = 80.0

// collViewWidth - sectionInsets
320.0 - (20.0 + 20.0) = 280.0

// spacing is only BETWEEN cells, not at end, so
//  add spacing
280.0 + 30.0 = 310.0

// how many cells will fit?
310.0 / 80.0 = 3.875

// can't have a "partial-cell" so 
floor(250.0 / 80.0) = 3.0
numCells = 3

// width of 3 cells
50.0 * 3.0 = 150.0

// "extra" space
280.0 - 150.0 = 130.0

// with 3 cells fitting, we have 2 "spaces"
130.0 / 2.0 = 65.0

actual spacing = 65.0

这里有一些示例代码来查看结果...

带有居中标签的简单单元格:

class CenterLabelCell: UICollectionViewCell {
    
    let label = UILabel()
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        label.backgroundColor = .yellow
        label.font = .systemFont(ofSize: 16.0, weight: .light)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(label)
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        ])
    }
}

视图控制器示例:

class CollViewSpacingVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    
    var collectionView: UICollectionView!
    
    let itemSpaces: [CGFloat] = [
        5.0, 10.0, 30.0,
    ]
    
    let sectionColors: [UIColor] = [
        .systemRed,   .systemRed.withAlphaComponent(0.5),
        .systemGreen, .systemGreen.withAlphaComponent(0.5),
        .systemBlue,  .systemBlue.withAlphaComponent(0.5),
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var layout: UICollectionViewFlowLayout {
            let layout = UICollectionViewFlowLayout()
            layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
            layout.minimumLineSpacing = 4
            layout.scrollDirection = .vertical
            layout.sectionInset = .init(top: 20.0, left: 20.0, bottom: 0.0, right: 20.0)
            return layout
        }
    
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
            collectionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            collectionView.widthAnchor.constraint(equalToConstant: 320.0),
        ])
        
        collectionView.register(CenterLabelCell.self, forCellWithReuseIdentifier: "c")
        collectionView.dataSource = self
        collectionView.delegate = self

        collectionView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)

    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return itemSpaces[section / 2]
    }
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return sectionColors.count
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return section % 2 == 0 ? 3 : 7
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "c", for: indexPath) as! CenterLabelCell
        cell.label.text = "\(Int(itemSpaces[indexPath.section / 2]))"
        cell.contentView.backgroundColor = sectionColors[indexPath.section]
        return cell
    }
    
}
© www.soinside.com 2019 - 2024. All rights reserved.