不滚动的 UICollectionView

Posted

技术标签:

【中文标题】不滚动的 UICollectionView【英文标题】:UICollectionView that does not scroll 【发布时间】:2017-08-06 21:14:41 【问题描述】:

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

我有 CollectionView 和 UIView 用于容器视图中的标题。标头固定在容器顶部,高度为 100 像素。 CollectionView 位于其下方,固定在每一侧,底部,其顶部固定在标题的底部。

当我使用 sizeForItemAt 时,我试图找到可见区域的大小以将其分成 1/3 大小的块(填充/插入除外)。我的代码如下:

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 行大约有一半被遮挡,因为看起来 frameSize 比它在模拟器中实际显示的“高”。

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

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

谢谢!

【问题讨论】:

必须是 UICollectionView 吗?根据您对所需内容的描述,我认为您最好使用嵌套的 StackViews 你应该使用集合视图中的部分标题;) 【参考方案1】:

你确定这个方法被调用了吗?在此例程中添加日志语句或断点并确保它被调用。

如果您忽略了正式声明您的视图控制器符合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: $0, section: 0) 
            .flatMap  layoutAttributesForItem(at: $0) 
    

    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

【讨论】:

老兄。伙计。网格布局第一次尝试是有效的——你是写的,还是在某个地方找到的?如果是你写的,我欠你一杯啤酒,如果你找到了,你能指出你在哪里写的吗?正是我需要的,非常感谢。 另外,代码确实被调用了,我已经正确设置了所有类,我只是不明白为什么 collectionView.bounds.size 和/或 collectionView.frame.size 没有给我正确的高度. if it's always going to be on a single screen 是什么意思?我想在某个时候用箭头按钮实现分页,我是不是用网格布局把自己画在一个角落里? 是的,我写了,但如果其他人写了类似的东西,我不会感到惊讶。关于为什么你最初的尝试没有成功,我不确定,但至少你现在有一个可行的解决方案。无论如何,我认为自定义布局是解决它的最佳方式。 如果您想稍后水平滚动(例如,当每页的数量超过某个值时),则必须更改自定义布局。这不是火箭科学,但需要一些工作。或者使用打开分页的水平流布局。【参考方案2】:

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

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

祝你好运! :)

【讨论】:

嗨,对不起,我不是更清楚。我实际上并不想要滚动视图 - 我想要在屏幕上显示所有内容(全尺寸),所以我不必滚动。我最初是使用嵌套堆栈视图来完成此操作的,但集合视图的灵活性似乎很有吸引力(我已经进入 ios 编程两周了,fwiw)【参考方案3】:

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

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

【讨论】:

嗨,约翰,感谢您的回复。我关闭了滚动 - 我想在不滚动的情况下查看集合视图中的所有单元格。在屏幕截图中,您可以看到底行被截断,我试图找出原因(我花了一整天的时间试图找出为什么 collectionView.frame 高度不正确 - 我很确定我的数学是。 我无法在接下来的几个小时内进行检查,但您应该打印您的 vales 以了解错误,我同意数学看起来不错。我首先要做的是使用 self.view 和大小......我稍后会检查【参考方案4】:

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

  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)
      
  

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

【讨论】:

以上是关于不滚动的 UICollectionView的主要内容,如果未能解决你的问题,请参考以下文章

【CSS】为啥设置不显示滚动条不好用呢?

如何在DataGrid里面产生滚动条而不滚动题头

【CSS】为啥设置不显示滚动条不好用呢?

滚动视图不滚动[重复]

如何让背景图片不随着上下滚动条滚动,但是可以随着左右滚动条滚动呢?

水平滚动视图不滚动