【问题标题】:UICollectionView remove section breaks with UICollectionViewFlowLayoutUICollectionView 使用 UICollectionViewFlowLayout 删除分节符
【发布时间】:2016-08-21 03:01:21
【问题描述】:

我有一个分为多个部分的数据集,但是,我想在一个 collectionView 中显示它,而不需要在部分之间进行中断。这是我想要实现的目标的说明:

Instead of:
0-0 0-1 0-2
0-3
1-0 1-1
2-0
3-0

I want:
0-0 0-1 0-2
0-3 1-0 1-1
2-0 3-0

我意识到解决方案可能在于自定义 UICollectionViewLayout 子类,但我不确定如何实现这样的目标。

谢谢

【问题讨论】:

  • 只使用一个部分将产生所需的结果。有什么理由需要使用多个部分吗?
  • 我正在使用一个 NSFetchedResultsController ,它有一个定义的 sectionNameKeyPath ,它将结果结果分成这些部分。这个控制器和缓存的结果在这个 collectionView 和另一个显示分节符的 tableView 之间共享——因此需要设置 sectionNameKeyPath。我意识到我可以做一些时髦的 NSIndexPath 切换,让它在 collectionView 的一个部分中工作,但我认为最干净的解决方案是调整流布局。
  • 我不相信没有子类化 UICollectionViewLayout 有一种干净的方法来实现你想要的。如果您使用两个单独的NSFetchedResultsControllers 会怎样?
  • 我不介意继承 UICollectionViewLayout。两个单独的 NSFetchedResultsController 是一个选项,但是我的 collectionView 和 tableView 需要通过在它们之间传递公共索引路径来相互交互。你有子类化 UICollectionViewLayout 路由的资源吗?这就是我想要弄清楚的。谢谢。

标签: ios objective-c swift uikit uicollectionviewlayout


【解决方案1】:

您需要继承 UICollectionViewLayout 是正确的。 开始之前要了解的本质是,您至少需要计算集合视图中每个单元格的位置和大小。 UICollectionViewLayout 只是提供该信息的结构化方式。你得到了结构,但你必须自己提供其他一切。

您需要重写 4 个方法:

  • 准备
  • 无效布局
  • layoutAttributesForItemAtIndexPath
  • layoutAttributesForElementsInRect

一个技巧是将布局属性缓存在查找表(字典)中:

var cachedItemAttributes = [IndexPath: UICollectionViewLayoutAttributes]()

prepare中,计算collectionView中每个indexPath的布局属性:

override func prepare() {
    super.prepare()
    calculateAttributes()
}

invalidateLayout 中重置缓存的布局属性并重新计算它们:

override func invalidateLayout() {
    super.invalidateLayout()
    cachedItemAttributes = [:]
    calculateAttributes()
} 

layoutAttributesForItemAtIndexPath 中,您使用查找表返回正确的布局属性:

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return cachedItemAttributes[indexPath]
}

layoutAttributesForElementsInRect 中过滤查找表以查找指定矩形内的元素:

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return cachedItemAttributes.filter { rect.intersects($0.value.frame) }.map { $0.value }
}

最后一块拼图是布局属性的实际计算。这里我只提供伪代码:

func calculateAttributes() {
    // For each indexpath (you can get this from the collectionView property using numberOfSections and numberOfItems:inSection )
    // calculate the frame, i.e the origin point and size of each cell in your collectionView and set it with UICollectionViewLayoutAttributes.frame
    // There are many other properties on UICollectionViewLayoutAttributes that you can tweak for your layout, but frame is a good starting point from which you can start experimenting.
    // Add the layout attributes to the lookup table
    // end loop
}

回答你的问题,下面是计算每个单元格位置的伪代码:

// If width of cell + current width of row + spacing, insets and margins exceeds the available width
// move to next row.
// else
// cell origin.x = current width of row + interitem spacing
// cell origin.y = number of rows * (row height + spacing)
// endif

如果您需要可配置自定义布局,则在可用签名足够的情况下使用 UICollectionViewDelegateFlowLayout,或者定义您自己的继承自 UICollectionViewDelegateFlowLayout 或 UICollectionViewDelegate。因为您的协议继承自 UICollectionViewDelegateFlowLayout,而后者本身继承自 UICollectionViewDelegate,所以您可以直接将其设置为 viewcontroller 中的 collectionView 委托。在您的自定义集合视图布局中,您只需要将委托从 UICollectionViewDelegate 转换为您的自定义协议即可使用它。请记住处理转换失败或委托未实现协议方法的情况。

【讨论】:

    【解决方案2】:

    我发现对我来说,Marmoy 的回答缺少一个额外的元素:

    覆盖collectionViewContentSize。

    否则,根据您的 collectionView 的大小,您可能会调用 layoutAttributesForElements(in rect: CGRect),它的宽度或高度为零,这将错过许多单元格。如果您尝试动态调整集合视图中的项目大小,则尤其如此。

    所以 Marmoy 的答案更完整的版本是:

    import UIKit
    
    class NoBreakSectionCollectionViewLayout: UICollectionViewLayout {
    
        var cachedItemAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
        var cachedContentSize = CGSize.zero
    
        override func prepare() {
            super.prepare()
            calculateAttributes()
        }
    
        override func invalidateLayout() {
            super.invalidateLayout()
            cachedItemAttributes = [:]
            calculateAttributes()
        }
    
        override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            return cachedItemAttributes[indexPath]
        }
    
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            return cachedItemAttributes.filter { rect.intersects($0.value.frame) }.map { $0.value }
        }
    
        override var collectionViewContentSize: CGSize {
            return cachedContentSize
        }
    
        func calculateAttributes() {
            var y = CGFloat(0)
            var x = CGFloat(0)
            var lastHeight = CGFloat(0)
            let xSpacing = CGFloat(5)
            let ySpacing = CGFloat(2)
            if let collectionView = collectionView, let datasource = collectionView.dataSource, let sizeDelegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
                let sections = datasource.numberOfSections?(in: collectionView) ?? 1
                for section in 0..<sections {
                    for item in 0..<datasource.collectionView(collectionView, numberOfItemsInSection: section){
                        let indexPath = IndexPath(item: item, section: section)
                        let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                        if let size = sizeDelegate.collectionView?(collectionView, layout: self, sizeForItemAt: indexPath) {
                            if x > 0 && (x + size.width + xSpacing) > collectionView.bounds.width {
                                y += size.height + ySpacing
                                x = CGFloat(0)
                            }
                            attributes.frame = CGRect(x: x, y: y, width: size.width, height: size.height)
                            lastHeight = size.height
                            x += size.width + xSpacing
                        }
                        cachedItemAttributes[indexPath] = attributes
                    }
                }
                cachedContentSize = CGSize(width: collectionView.bounds.width, height: y + lastHeight)
            }
    
        }
    }
    

    此外,在上面的示例中实现 UICollectionViewDelegateFlowLayout 对您的委托很重要...或者,如果您知道布局中的项目大小而不知道单元格内容,则可以只计算它们。

    【讨论】:

    • 开箱即用,非常好:)。可以使用 init 进行改进,为这些常量(间距等)提供值
    猜你喜欢
    • 1970-01-01
    • 2017-04-02
    • 2012-12-25
    • 2019-11-06
    • 2021-06-02
    • 2012-12-06
    • 1970-01-01
    • 2013-08-27
    • 1970-01-01
    相关资源
    最近更新 更多