UICollectionViewCompositionalLayout最小项目大小



我希望UICollectionView的最小项目大小为150x150。我希望组合布局确定有多少150x150单元可以安全地适应环境,然后在必要时从150x150增加大小以填充间隙,但保持使用150x150单元格大小计算的相同列数。

以下是我的尝试,它在纵向和横向都没有保持150x150的最小单元格大小。

let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, environment) -> NSCollectionLayoutSection? in
let fraction: CGFloat = 1 / (floor((environment.container.effectiveContentSize.width / 150)))
let inset: CGFloat = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(fraction), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(fraction))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset)
return section
})
collectionView.collectionViewLayout = compositionalLayout

我认为解决这类问题的方法是首先解决你能想到的最退化、最简单的情况,然后添加任何所需的改进。

所以,让我们忽略所有关于间距的东西,问问自己:我们如何制作一个由150个或更多正方形单元格组成的简单网格?这些单元格将接触集合视图的侧面,并在所有侧面上相互接触;在这种退化的情况下不存在任何类型的间距。

这里有一种方法。我们将使用一个部分提供者函数,这样我们就可以知道集合视图的大小:

let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { 
(sectionIndex, environment) -> NSCollectionLayoutSection? in

集合视图的宽度中可以容纳多少个这样的正方形?这是整数除法中的一个简单问题:

let w = environment.container.effectiveContentSize.width
let max = Int(w) / 150

现在,我们将把每行的项目数指定为一个数字(max(,因此项目宽度无关紧要;我们将使用一个伪值。物品高度将是团队高度,当我们到达时,我们将跨过这座桥:

let itemSize = NSCollectionLayoutSize(
widthDimension: .absolute(10), // this is a dummy value
heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

好吧,让我们过桥吧。组高度需要与项目宽度相同,以便使单元格为方形。项目宽度将是集合视图宽度除以每组项目数;这是我们已经计算出的max:

let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .absolute(w/CGFloat(max))) // square
let group = NSCollectionLayoutGroup
.horizontal(layoutSize: groupSize, subitem: item, count: max)

我们完成了:

let section = NSCollectionLayoutSection(group: group)
return section

试试看。如果你的单元格有边框,这样你就可以看到单元格的边缘在哪里,这会有所帮助。你会看到,我们现在有大于150的方形单元格,无论是纵向单元格还是横向单元格,当然,纵向单元格的尺寸与横向单元格的尺寸不同。

好的,在解决了退化情况的问题之后,让我们考虑一个更复杂的情况。让我们在所有东西之间假设一个10点的空间:外部的10点边距,加上单元格之间垂直和水平的10点。我们所要做的就是添加三行:

group.interItemSpacing = .fixed(10)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 10
section.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10)

事实上,这看起来很不错,但我们的数学不再正确。我们可以看到这一点,因为细胞不再是正方形的。

所以,让我们把插入和空格插入到我们的数学中。我们单元格区域的最大宽度现在是160,而不是150,因为单元格每侧将被剥夺5个点。集合视图的宽度必须减少10个点,因为它将在一侧被抢去10个点(即20个(,但通过我们刚才的计算,其中10个点被放回了。所以,把它带到任何地方,我们最终得到了这个:

let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: {
(sectionIndex, environment) -> NSCollectionLayoutSection? in

// how many 150-or-larger squares can fit in our width?
let w = environment.container.effectiveContentSize.width - 10
let max = Int(w) / (150 + 10)

let itemSize = NSCollectionLayoutSize(
widthDimension: .absolute(10), // this is a dummy value
heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .absolute(w/CGFloat(max) - 10)) // square
let group = NSCollectionLayoutGroup
.horizontal(layoutSize: groupSize, subitem: item, count: max)

group.interItemSpacing = .fixed(10)

let section = NSCollectionLayoutSection(group: group)

section.interGroupSpacing = 10
section.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10)

return section
})

看起来不错!这是一个没有神奇数字的更通用的版本。我们从提取所有的幻数作为属性开始,加上我通过改变一些数字(裕度(进行了推广:

var square = 150
var padding : CGFloat = 10
var margin : CGFloat = 16

我们的布局现在看起来是这样的:

let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: {
[self] (sectionIndex, environment) -> NSCollectionLayoutSection? in

let w = environment.container.effectiveContentSize.width - CGFloat(margin)*2 + padding
let max = Int(w) / (square + Int(padding))

let itemSize = NSCollectionLayoutSize(
widthDimension: .absolute(10), // this is a dummy value
heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .absolute(w/CGFloat(max) - padding)) // square
let group = NSCollectionLayoutGroup
.horizontal(layoutSize: groupSize, subitem: item, count: max)

group.interItemSpacing = .fixed(padding)

let section = NSCollectionLayoutSection(group: group)

section.interGroupSpacing = padding
section.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)

return section
})

这似乎确实是我们想要的!为了确保这一点,我为它写了一个测试用例:

let vc = ViewController(collectionViewLayout: UICollectionViewFlowLayout())
vc.loadViewIfNeeded()
vc.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
for _ in 1..<3 {
vc.margin = CGFloat(Int.random(in:7..<20))
vc.padding = CGFloat(Int.random(in:7..<20))
vc.square = Int.random(in:140..<170)
var foundMin = false
for w : CGFloat in stride(from: 280, to: 500, by: 1) {
for h : CGFloat in stride(from: 280, to: 800, by: 10) {
vc.view.frame = CGRect(x: 0, y:0, width: w, height: h)
vc.view.layoutIfNeeded()
let cell = vc.collectionView.visibleCells.first!
let sz = cell.bounds.size
XCTAssertEqual(sz.width, sz.height, accuracy: 0.1)
XCTAssertGreaterThanOrEqual(sz.width, CGFloat(vc.square))
if sz.width == CGFloat(vc.square) { foundMin = true }
}
}
XCTAssertTrue(foundMin)
}

这里的想法是证明我们的单元格总是正方形的,它们有时是所需的最小值,但永远不会少——这正是你在问题中强加的条件。我们使用依赖注入来随机化初始条件。

最新更新