我正在使用
UICollectionView
处理 UICollectionViewFlowLayout
,并且在理解项目大小间距方面存在一些困难。我知道有几种方法可以调整大小和间距(使用委托方法、覆盖 FlowLayout 等)。 然而,如果不首先理解这些值背后的逻辑,就很难正确地调整它们。
以下结果已创建默认
UICollectionViewController
,其中默认 UICollectionViewCell
没有任何子类。仅进行了以下设置:
代码:
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
使用不同的值会产生我不明白的结果:
...sizeForItemAt
来指定显式尺寸。不过,在 IB 中设置大小应该也可以,不是吗?为什么 200 x 200px 的 IB 尺寸被忽略?为什么是 50x50,这是默认大小还是在哪里指定的?minimumInteritemSpacing
没有设置显式间距,而只是设置 最小。然而,这个值是如何计算的呢?7.5px
的间距值为 5?为什么结果值 10-25 的结果都相同,间距为 26.5px
?那么:这里的尺寸和间距到底是如何计算的?
您有多个问题(不太推荐),我会尽我所能回答。
为什么项目的尺寸为 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()
,会发生什么。
正如您从 Larme 中了解到的那样,您的 Storyboard 布局将被忽略,因为您创建了一个新布局。
当集合视图布置其单元格时,它会说:
.itemSize
吗?.zero
的大小(您的单元格没有影响其大小的约束).estimatedItemSize
在您的情况下,您设置
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
这意味着 “使用默认值 (50, 50)
”。
“那么:这里的尺寸和间距到底是如何计算的?”
好吧,让我们看看更多的细胞。
我们将使用 6 个部分...
.minimumInteritemSpacing = 5.0
.minimumInteritemSpacing = 10.0
.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
}
}