【问题标题】:UICollectionViewCompositionalLayout minimum item sizeUICollectionViewCompositionalLayout 最小项目大小
【发布时间】:2021-04-18 04:04:27
【问题描述】:

我希望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

【问题讨论】:

  • 一个问题是您正在使用项目内容插入,这会将单元格缩小到小于其实际高度和宽度。您可能打算使用项目间间距。
  • 很好,但我怀疑这是问题的唯一原因,因为横向模式下的单元格远小于 150x150。

标签: ios swift uicollectionview


【解决方案1】:

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

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

这是一种方法。我们将使用一个 section provider 函数,以便我们知道集合视图的大小:

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)
}

这里的想法是表明我们的单元格始终是正方形的,并且它们有时是所需的最小值,但永远不会小于 - 这正是您在问题条款中施加的条件。我们使用依赖注入来随机化初始条件。

【讨论】:

  • 感谢您的详细描述!对我来说,关键是您答案的“将插入和间距插入我们的数学”部分。这看起来不错,但是,在横向模式下,我可以获得具有不同宽度和高度的单元格大小。我不明白为什么它不会一直被强制为正方形?
  • 在我的代码测试中,纵向和横向都是正方形。
  • 使用 EffectiveContentSize 解决了这个问题!
  • 但是我不明白为什么我也不必使用effectiveContentSize。其他事情可能正在发生。在我的代码中,contentSizeeffectiveContentSize 是相同的。因此,如果它们对您来说不一样,那么您一定是在做一些您没有告诉我们的事情。
  • 我倾向于认为我的代码是正确的。我已经为它添加了一个 XCTestCase。
猜你喜欢
  • 1970-01-01
  • 2012-04-19
  • 1970-01-01
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
  • 2014-03-27
  • 2011-06-15
  • 1970-01-01
相关资源
最近更新 更多