假设您有一个带有普通自定义 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 怎么办?
估计的ItemSize 在哪里?
据我所知,UICollectionViewLayout 中没有estimatedItemSize 概念?
那你到底在做什么?
您可以天真地 - 在上面的代码中 - 简单地以一种或另一种方式计算每个单元格的最终高度(例如计算任何文本块的高度等)。但这似乎非常低效:在计算整个 100 个单元格大小之前,可以绘制任何集合 View 。您根本不会使用任何 iOS 的动态高度功能,并且没有任何东西是及时的。
我想,您可以从头开始对整个即时系统进行编程。 (所以,像 .. 使表格大小实际上只有 1,手动计算该高度,将其发送到集合 View ;计算项目 2 的高度,将其发送,等等。)但这很蹩脚。
有什么方法可以使用自定义 UICollectionViewLayout 实现动态高度单元格 - 而不是流布局?
(同样,当然很明显你可以手动完成,所以在上面的代码中一次计算所有 1000 个高度,你就完成了,但这会很蹩脚。)
就像我在上面说的第一个难题是,(正常,非流)UICollectionViewLayout 中的“估计大小”概念到底在哪里?
最佳答案
只是一个警告:自定义布局远非微不足道,他们可能值得自己写一篇研究论文;)
您可以 在您自己的布局中实现大小估计和动态调整大小。实际上,估计的尺寸没什么特别的。相反,动态大小是。因为自定义布局会给你一个 总计 控制一切,然而,这涉及许多步骤。您需要在布局子类中实现三种方法,在单元格中实现一种方法。
preferredLayoutAttributesFitting(_:)
在您的单元格中(或更一般地说,可重用的 View 子类)。在这里你可以使用任何你想要的计算。您可能会对单元格使用自动布局:如果是这样,您将需要将所有单元格的 subview 添加到其 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
}
shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)
在布局对象上将被调用。重要的是,您不能只是简单地输入 return true
,因为系统将无限期地重新询问单元格。这实际上非常聪明,因为许多单元格可能会对彼此的更改使用react,因此布局最终决定是否满足单元格的愿望。通常,为了调整大小,你会写这样的东西:override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
if preferredAttributes.size.height.rounded() != originalAttributes.size.height.rounded() {
return true
}
return false
}
invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
将被调用。您通常希望自定义上下文类来存储特定于您的布局的信息。一个重要的、相当不直观的警告是你应该 不是 调用 context.invalidateItems(at:)
因为这会导致布局失效 只有提供的索引路径中实际可见的那些项目。只需跳过此方法,因此布局将重新查询可见矩形。然而!是否需要设置
contentOffsetAdjustment
需要慎重考虑和 contentSizeAdjustment
:如果调整大小,您的集合 View 作为一个整体可能会缩小或扩大。如果您不考虑这些,滚动时将有跳转重新加载。 invalidateLayout(with:)
将被调用。这是您实际调整部分/行高的步骤,移动受调整单元格影响的内容等。如果您覆盖,您将需要调用 super
. PS:这真的是一个很难的话题,我只是触及了表面。你可以看看here它变得多么复杂(但这个 repo 也是一个非常丰富的学习工具)。
关于具有动态高度的 UICollectionViewLayout - 但不使用流布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58250053/