Swift - 使用 UICollectionViewFlowLayout 的 Facebook 风格图像网格

Swift - Facebook style image grid using UICollectionViewFlowLayout

我正在尝试在 Swift 中实现 Facebook 风格的图像布局。我找到 并遵循其中一个答案建议的方法。结果,我几乎实现了我想要的。

但是,正如您在上图中看到的那样,有一个溢出:三个较小的单元格占用的空间似乎超过了屏幕宽度。但是,如果我将0.1减去每个单元格的宽度,问题似乎已经解决了。

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let item = indexPath.item
        let width = collectionView.bounds.size.width
        let padding:CGFloat = 5
        if item < 2 {
            let itemWidth:CGFloat = (width - padding) / 2.0
            return CGSize(width: itemWidth, height: itemWidth)
        } else {
            let itemWidth:CGFloat = (width - 2 * padding) / 3.0
            // let itemWidth:CGFloat = (width - 2 * padding) / 3.0 - 0.1  // This WORKS!!
            return CGSize(width: itemWidth, height: itemWidth)
        }
    }

我猜溢出是由 itemWidth 的值向上舍入引起的。但我觉得在处理此类问题时,减去一个随机的硬编码值并不是最佳做法。

谁能为此提出更好的方法?

以下是可重现的完整代码。


class ContentSizeCollectionView: UICollectionView {
    override var contentSize: CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }
    
    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

class GridVC: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource {
    lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.minimumLineSpacing = 5
        layout.minimumInteritemSpacing = 5
        let collectionView = ContentSizeCollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "ident")
        return collectionView
    }()
    
    override func viewDidLoad() {
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let item = indexPath.item
        let width = collectionView.bounds.size.width
        let padding:CGFloat = 5
        if item < 2 {
            let itemWidth:CGFloat = (width - padding) / 2.0
            return CGSize(width: itemWidth, height: itemWidth)
        } else {
            let itemWidth:CGFloat = (width - 2 * padding) / 3.0
            return CGSize(width: itemWidth, height: itemWidth)
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ident", for: indexPath)
        cell.backgroundColor = .red
        return cell
    }
}

更新

floor((width - 2 * padding) / 3.0)确实防止了溢出,但最后留下了一个小空隙。

更改此行:

let itemWidth:CGFloat = (width - 2 * padding) / 3.0

对此:

let itemWidth:CGFloat = floor((width - 2 * padding) / 3.0)

您可能还想对 2 项行执行此操作。


编辑

最初的问题是浮点数是近似值。

因此,例如,在 iPhone 8 上,视图宽度为 375 点。单元格宽度计算为:

let padding: CGFloat = 5
let itemWidth:CGFloat = (width - 2 * padding) / 3.0

itemWidth 最终成为 121.666... (repeating),UIKit 将其解释为 121.66666666666667.

因此,121.66666666666667 * 3.0 + 10 等于(大致)375.00000000000011 并且...大于 375.0

因此,集合视图显示“不能将它放在一行中。”

使用 floor() 解决了这个问题,除了... 它遇到了一个非常奇怪的错误!

如果将 numberOfItemsInSection5 更改为 8,您将得到两行 3,并且会有 no gap右边.

我们可以通过使 side 单元格比 center 单元格稍微窄一些来解决这个问题,如下所示:

// we want all three to be the same heights
//  1/3 of (width - 2 * padding)
let itemHeight: CGFloat = (width - 2 * padding) / 3.0

// left and right cells will be 1/3 of (width - 2 * padding)
//  rounded down to a whole number
let sideW: CGFloat = floor(itemHeight)

// center cell needs to be
//  full-width minus 2 * padding
//  minus
//  side-width * 2
let centerW: CGFloat = (width - 2 * padding) - sideW * 2
        
// is it left (0), center (1) or right (2)
let n = (item - 2) % 3
        
// use the proper width value
let itemWidth: CGFloat = n == 1 ? centerW : sideW

return CGSize(width: itemWidth, height: itemHeight)

或者,似乎 起作用的是使宽度 比浮点数的 1/3 略小。您使用了 -0.1,但它也适用于:

let itemWidth:CGFloat = ((width - 2 * padding) / 3.0) - 0.0000001

无论如何,这有望解释“2 个单元而不是 3 个”问题的原因,以及避免它的两个选项。