【问题标题】:UICollectionViewLayout with dynamic heights - but NOT using a flow layout具有动态高度的 UICollectionViewLayout - 但不使用流布局
【发布时间】:2020-02-03 14:21:18
【问题描述】:

假设您有一个带有普通自定义 UICollectionViewLayout 的 UICollectionView。

所以是 >>> 不是

自定义布局是微不足道的,在prepare 调用中,您只需遍历数据并布局每个矩形。所以说它是一个垂直滚动的集合......

override func prepare() {
    cache = []
    var y: CGFloat = 0
    let k = collectionView?.numberOfItems(inSection: 0) ?? 0
    // or indeed, just get that direct from your data
    
    for i in 0 ..< k {
        
        // say you have three cell types ...
        let h = ... depending on the cell type, say 100, 200 or 300
        
        let f = CGRect(
            origin: CGPoint(x: 0, y: y ),
            size: CGSize(width: screen width, height: h)
        )
        
        y += thatHeight
        y += your gap between cells
        
        cache.append( .. that one)
    }
}

在示例中,对于上述三种单元格类型中的每一个,单元格高度都是固定的 - 都没有问题。

处理动态单元格高度如果您使用的是流式布局,这是一个很好的探索并且确实相对简单。 (Example,也可以在www上看到很多解释。)

但是,如果您想要具有(NON-flow)完全正常的日常 UICollectionViewLayout 的动态单元格高度怎么办?

estimatedItemSize 在哪里?

据我所知,UICollectionViewLayout 中没有estimatedItemSize 的概念?

那你到底是做什么的?

您可以天真地只是 - 在上面的代码中 - 以一种或另一种方式简单地计算每个单元格的最终高度(例如计算任何文本块的高度等)。但这似乎非常低效:在计算整个 100 个单元格大小之前,根本无法绘制任何集合视图。您根本不会使用任何 iOS 的动态高度功能,也不会是即时的。

我想,您可以从头开始对整个即时系统进行编程。 (所以,像 .. 使表格大小实际上只有 1,手动计算该高度,将其发送到集合视图;计算项目 2 的高度,将其发送,等等。)但这很蹩脚。

有没有办法使用自定义 UICollectionViewLayout 实现动态高度单元格 - 而不是流布局?

(当然,当然你也可以手动完成,所以在上面的代码中一次计算所有 1000 个高度,你就完成了,但这会很蹩脚。)

就像我在上面所说的第一个难题是,(正常的、非流动的)UICollectionViewLayout 中的“估计大小”概念到底在哪里?

【问题讨论】:

    标签: uicollectionview ios12 swift5


    【解决方案1】:

    只是一个警告:自定义布局远非微不足道,它们可能值得自己写一篇研究论文;)

    可以在自己的布局中实现尺寸估计和动态调整大小。实际上,估计的大小并没有什么特别之处。相反,动态尺寸是。但是,因为自定义布局可以让您完全控制所有内容,所以这涉及到许多步骤。您需要在布局子类中实现三个方法,在单元格中实现一个方法。

    1. 首先,您需要在单元格中实现preferredLayoutAttributesFitting(_:)(或者,更一般地说,可重用视图子类)。在这里,您可以使用任何您想要的计算。您可能会对单元格使用自动布局:如果是这样,您需要将所有单元格的子视图添加到其contentView,将它们约束到边缘,然后在此“首选属性”方法中调用systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority:)。例如,如果您希望您的单元格垂直调整大小,同时受到水平限制,您可以这样写:

      override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
      
                  // Ensures that cell expands horizontally while adjusting itself vertically.
                  let preferredSize = systemLayoutSizeFitting(layoutAttributes.size, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
      
                  layoutAttributes.size = preferredSize
                  return layoutAttributes
              }
      
    2. 在向单元格询问其首选属性后,将调用布局对象上的shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)。重要的是,您不能只是简单地输入return true,因为系统会无限期地重新询问单元格。这实际上非常聪明,因为许多单元格可能会对彼此的更改做出反应,因此最终决定是否完成满足单元格愿望的是布局。通常,为了调整大小,你会这样写:

      override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
      
                  if preferredAttributes.size.height.rounded() != originalAttributes.size.height.rounded() {
                      return true
                  }
                  return false
              }
      
    3. 在那之后,invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:) 将被调用。您通常希望自定义上下文类以存储特定于您的布局的信息。一个重要但不直观的警告是,您应该调用context.invalidateItems(at:),因为这将导致布局使在提供的索引路径中实际上是可见的。跳过这个方法,布局会重新查询可见矩形。

      但是!您需要彻底考虑是否需要设置contentOffsetAdjustmentcontentSizeAdjustment:如果调整大小,您的集合视图作为一个整体可能会缩小或扩大。如果你不考虑这些,你会在滚动时跳转重新加载。

    4. 最后,invalidateLayout(with:) 将被调用。这是您实际调整部分/行高度、移动受调整单元格大小影响的内容等的步骤。如果您覆盖,您将需要调用super

    PS:这确实是一个很难的话题,我只是触及了表面。你可以看看here它有多复杂(但是这个repo也是一个非常丰富的学习工具)。

    【讨论】:

      猜你喜欢
      • 2018-07-26
      • 2021-05-10
      • 1970-01-01
      • 2012-12-09
      • 2013-07-04
      • 1970-01-01
      • 2017-04-07
      • 2013-05-25
      • 2016-03-03
      相关资源
      最近更新 更多