UICollectionViewCompositionalLayout 最小项目大小

use*_*032 4 ios uicollectionview swift

我希望我的最小项目尺寸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
Run Code Online (Sandbox Code Playgroud)

mat*_*att 8

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

\n

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

\n

这是一种方法。我们将使用部分提供程序函数,以便我们知道集合视图的大小是多少:

\n
let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { \n    (sectionIndex, environment) -> NSCollectionLayoutSection? in\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
    let w = environment.container.effectiveContentSize.width\n    let max = Int(w) / 150\n
Run Code Online (Sandbox Code Playgroud)\n

现在,我们将把每行的项目数指定为数字(max ),因此项目宽度并不重要;我们将使用一个虚拟值。项目高度将是组高度,当我们到达它时我们将跨过那座桥:

\n
    let itemSize = NSCollectionLayoutSize(\n        widthDimension: .absolute(10), // this is a dummy value\n        heightDimension: .fractionalHeight(1))\n    let item = NSCollectionLayoutItem(layoutSize: itemSize)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
    let groupSize = NSCollectionLayoutSize(\n        widthDimension: .fractionalWidth(1),\n        heightDimension: .absolute(w/CGFloat(max))) // square\n    let group = NSCollectionLayoutGroup\n        .horizontal(layoutSize: groupSize, subitem: item, count: max)\n
Run Code Online (Sandbox Code Playgroud)\n

我们就完成了:

\n
    let section = NSCollectionLayoutSection(group: group)\n    return section\n
Run Code Online (Sandbox Code Playgroud)\n

试试看。如果您的单元格有边框,那么您可以看到单元格边缘的位置,这将会有所帮助。您将看到,无论是纵向还是横向,我们现在都有大于 150 的方形单元格,当然纵向和横向的单元格尺寸是不同的。

\n

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

\n
    group.interItemSpacing = .fixed(10)\n    let section = NSCollectionLayoutSection(group: group)\n    section.interGroupSpacing = 10\n    section.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

因此,让我们将插入和间距插入到我们的数学中。我们单元格区域的最大宽度现在是 160,而不是 150,因为单元格每边都会被抢走 5 个点。并且集合视图的宽度必须减少 10 个点,因为它将在一侧抢走 10 个点,即 20,但是其中 10 个点被我们刚才的计算放回去了。因此,将其贯穿到各处,我们最终会得到这样的结果:

\n
let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: {\n    (sectionIndex, environment) -> NSCollectionLayoutSection? in\n    \n    // how many 150-or-larger squares can fit in our width?\n    let w = environment.container.effectiveContentSize.width - 10\n    let max = Int(w) / (150 + 10)\n    \n    let itemSize = NSCollectionLayoutSize(\n        widthDimension: .absolute(10), // this is a dummy value\n        heightDimension: .fractionalHeight(1))\n    let item = NSCollectionLayoutItem(layoutSize: itemSize)\n    \n    let groupSize = NSCollectionLayoutSize(\n        widthDimension: .fractionalWidth(1),\n        heightDimension: .absolute(w/CGFloat(max) - 10)) // square\n    let group = NSCollectionLayoutGroup\n        .horizontal(layoutSize: groupSize, subitem: item, count: max)\n    \n    group.interItemSpacing = .fixed(10)\n    \n    let section = NSCollectionLayoutSection(group: group)\n    \n    section.interGroupSpacing = 10\n    section.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10)\n    \n    return section\n})\n
Run Code Online (Sandbox Code Playgroud)\n

看起来不错!这是一个更通用的版本,没有神奇的数字。我们首先提取所有幻数作为属性,另外我通过更改一些数字(边距)进行了概括:

\n
var square = 150\nvar padding : CGFloat = 10\nvar margin : CGFloat = 16\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: {\n    [self] (sectionIndex, environment) -> NSCollectionLayoutSection? in\n                \n    let w = environment.container.effectiveContentSize.width - CGFloat(margin)*2 + padding\n    let max = Int(w) / (square + Int(padding))\n    \n    let itemSize = NSCollectionLayoutSize(\n        widthDimension: .absolute(10), // this is a dummy value\n        heightDimension: .fractionalHeight(1))\n    let item = NSCollectionLayoutItem(layoutSize: itemSize)\n    \n    let groupSize = NSCollectionLayoutSize(\n        widthDimension: .fractionalWidth(1),\n        heightDimension: .absolute(w/CGFloat(max) - padding)) // square\n    let group = NSCollectionLayoutGroup\n        .horizontal(layoutSize: groupSize, subitem: item, count: max)\n    \n    group.interItemSpacing = .fixed(padding)\n    \n    let section = NSCollectionLayoutSection(group: group)\n    \n    section.interGroupSpacing = padding\n    section.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)\n    \n    return section\n})\n
Run Code Online (Sandbox Code Playgroud)\n

这似乎确实达到了我们想要的效果!为了确保这一点,我为其编写了一个测试用例:

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

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

\n