不滚动的 UICollectionView

UICollectionView that does not scroll

我正在尝试创建一个所有单元格都 100% 可见的 UICollectionView,这样我就不需要滚动来查看所有单元格。我目前正在尝试显示 3x3 网格,并即时计算单元格的大小。

我在容器视图中有一个 header 的 CollectionView 和一个 UIView。 header 固定在容器顶部,高度为 100px。 CollectionView 位于其下方,固定在底部的每一侧,其顶部固定在 header.

的底部

当我使用 sizeForItemAt 时,我试图找到可见区域的大小以将其分成 1/3 大小的块(padding/insets 放在一边)。我的代码如下所示:

func collectionView(_ collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                    sizeForItemAt indexPath: IndexPath) -> CGSize {

    let numRows = self.numRows()
    let itemsPerRow = self.itemsPerRow()

//        let frameSize = collectionView.frame.size
    let frameSize = collectionView.bounds.size
//        let frameSize = collectionView.collectionViewLayout.collectionViewContentSize
//        let frameSize = collectionView.intrinsicContentSize

    let totalItemPadding = self.itemPadding * (itemsPerRow - 1)
    let totalLinePadding = self.linePadding * (numRows - 1)

    let availableWidth = frameSize.width - totalItemPadding
    var widthPerItem = availableWidth / itemsPerRow

    let availableHeight = frameSize.height - totalLinePadding
    var heightPerItem = availableHeight / numRows

    return CGSize(width: widthPerItem, height: heightPerItem)
}

结果始终是第 3 行大约 half-obscured,因为看起来 frameSize 比模拟器中实际显示的要 "taller"。

UICollectionView 中有什么东西可以给我可见的尺寸吗?我在布局时间方面是否处于错误的时间,或者我应该添加另一种在某些时候使尺寸无效的方法?

我还没有找到关于显示所有项目且不垂直滚动的 collection 视图的任何教程,因此任何其他指针(甚至是执行类似操作的库)都会非常有用赞赏。

谢谢!

对于布局大小,您可以使用 UICollectionViewFlowLayout 并根据屏幕宽度和高度关联尺寸,以保持 UICollectionView. 的网格样式外观,请参阅:https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout and https://www.raywenderlich.com/136159/uicollectionview-tutorial-getting-started.

关于您的滚动问题,请检查您的滚动是否已启用,您的约束设置是否正确,并且您没有link 中提到的问题。 UICollectionView does not scroll

祝你好运! :)

您的滚动不起作用,因为视图只是屏幕大小...

只是为了获得技巧,添加一个 1000 的偏移量。collectionView 的总高度的量是每一行 * (row.size.height + padding)。

您确定正在调用此方法吗?在此例程中添加日志语句或断点,并确保它被调用。

如果您忽略了正式声明您的视图控制器以符合 UICollectionViewDelegateFlowLayout,将阻止此调用的一个常见问题。在这种情况下,它将使用它在故事板中找到的任何内容。但是当我这样做时,您的代码对我来说工作正常,例如:

extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        let numRows = self.numRows()
        let itemsPerRow = self.itemsPerRow()

        let frameSize = collectionView.bounds.size

        let layout = collectionViewLayout as! UICollectionViewFlowLayout

        let totalItemPadding = layout.minimumInteritemSpacing * (itemsPerRow - 1)
        let totalLinePadding = layout.minimumInteritemSpacing * (numRows - 1)

        let availableWidth = frameSize.width - totalItemPadding
        let widthPerItem = availableWidth / itemsPerRow

        let availableHeight = frameSize.height - totalLinePadding
        let heightPerItem = availableHeight / numRows

        return CGSize(width: widthPerItem, height: heightPerItem)

    }
}

请注意,我还使用了 minimumInteritemSpacing,因此我使用现有的间距参数而不是定义您自己的间距参数。我觉得最好使用现有参数(尤其是您也可以在 IB 中设置的参数)。


顺便说一句,如果总是在一个屏幕上,另一种方法是使用您自己的自定义布局,而不是流式布局。这样您就不会将集合视图的委托与大量繁琐的代码纠缠在一起。它会更可重用。例如:

class GridLayout: UICollectionViewLayout {

    var itemSpacing: CGFloat = 5
    var rowSpacing: CGFloat = 5

    private var itemSize: CGSize!
    private var numberOfRows: Int!
    private var numberOfColumns: Int!

    override func prepare() {
        super.prepare()

        let count = collectionView!.numberOfItems(inSection: 0)

        numberOfColumns = Int(ceil(sqrt(Double(count))))
        numberOfRows = Int(ceil(Double(count) / Double(numberOfColumns)))

        let width = (collectionView!.bounds.width - (itemSpacing * CGFloat(numberOfColumns - 1))) / CGFloat(numberOfColumns)
        let height = (collectionView!.bounds.height - (rowSpacing * CGFloat(numberOfRows - 1))) / CGFloat(numberOfRows)
        itemSize = CGSize(width: width, height: height)
    }

    override var collectionViewContentSize: CGSize {
        return collectionView!.bounds.size
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)

        attributes.center = centerForItem(at: indexPath)
        attributes.size = itemSize

        return attributes
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return (0 ..< collectionView!.numberOfItems(inSection: 0)).map { IndexPath(item: [=11=], section: 0) }
            .flatMap { layoutAttributesForItem(at: [=11=]) }
    }

    private func centerForItem(at indexPath: IndexPath) -> CGPoint {

        let row = indexPath.item / numberOfColumns
        let col = indexPath.item - row * numberOfColumns

        return CGPoint(x: CGFloat(col) * (itemSize.width + itemSpacing) + itemSize.width / 2,
                       y: CGFloat(row) * (itemSize.height + rowSpacing) + itemSize.height / 2)
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}

然后,在视图控制器中:

override func viewDidLoad() {
    super.viewDidLoad()

    let layout = GridLayout()
    layout.itemSpacing = 10
    layout.rowSpacing = 5
    collectionView?.collectionViewLayout = layout
}

在我的例子中,这要简单得多,基本思想是在集合视图的末尾添加额外的填充。在我的情况下,我的方向是水平的,但同样适用于垂直方向(尚未测试那个)技巧是调整委托功能如下:

  extension ViewController: UICollectionViewDelegateFlowLayout {

        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    
            return UIEdgeInsets.init(top: collectionView.bounds.width * 0.02, left: collectionView.bounds.width * 0.02, bottom: collectionView.bounds.width * 0.02, right: collectionView.bounds.width * 0.22)
      }
  }

注意到正确值的差异,在您的情况下,它将是行的底部值。