UICollectionViewCompositionalLayout 最小项目大小
UICollectionViewCompositionalLayout minimum item size
我希望 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
),因此项目宽度无关紧要;我们将使用虚拟值。 item的高度就是group的高度,到了那座桥就过桥了:
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 点的 space:外侧 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 个点。 collection view 的宽度必须减少 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)
}
这里的想法是表明我们的单元格始终是方形的,并且它们有时是所需的最小值但绝不会小于 - 这正是您在问题中施加的条件。我们使用依赖注入来随机化初始条件。
我希望 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
),因此项目宽度无关紧要;我们将使用虚拟值。 item的高度就是group的高度,到了那座桥就过桥了:
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 点的 space:外侧 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 个点。 collection view 的宽度必须减少 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)
}
这里的想法是表明我们的单元格始终是方形的,并且它们有时是所需的最小值但绝不会小于 - 这正是您在问题中施加的条件。我们使用依赖注入来随机化初始条件。