假设您有一个带有普通自定义UICollectionViewLayout的UICollectionView。
即>>> NOT <<
自定义布局是微不足道的,在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动态单元格高度怎么办?
据我所知,UICollectionViewLayout中没有EstimatedItemSize概念?
那你到底干什么?
您可以天真地-在上面的代码中-仅以一种或另一种方式简单地计算每个单元格的最终高度(例如,计算任何文本块的高度,等等)。但这似乎效率很低:在计算出整个100的像元大小之前,无法绘制所有的收集视图。您根本不会使用iOS的任何动态高度功能,并且没有任何即时功能。
我想,您可以从头开始对整个实时系统进行编程。 (因此,..之类的东西实际上使表格的大小仅为1,手动计算该高度,将其发送到集合视图;计算项目2的高度,随其发送,依此类推。)但这很la脚。
是否有任何方法可以通过自定义UICollectionViewLayout实现动态高度单元-而不是流布局?
(再次,当然,显然您可以手动完成,因此在上面的代码中一次计算所有1000个高度,就可以了,但是那简直是la脚。)
就像我在第一个难题上说的那样,(正常,非流式)UICollectionViewLayout中的“估计大小”概念到底在哪里?
只是警告:自定义布局是微不足道的,它们可能值得自己研究;)
您可以在您自己的布局中实现尺寸估计和动态大小调整。实际上,估计大小没有什么特别的。而是动态尺寸。因为自定义布局使您可以对所有内容进行total控制,所以这涉及许多步骤。您将需要在布局子类中实现三种方法,在单元格中实现一种方法。
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
}
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
}
invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
。通常,您需要自定义上下文类以存储特定于布局的信息。一个重要但不直观的警告是,您应该not调用context.invalidateItems(at:)
,因为这将导致布局使only所提供的实际上可见的索引路径中的那些项无效。只需跳过此方法,布局将重新查询可见的矩形。但是!您需要仔细考虑是否需要设置contentOffsetAdjustment
和contentSizeAdjustment
:如果调整大小,则整个集合视图可能会缩小或扩展。如果您不考虑这些因素,则滚动时将具有跳转重载。
invalidateLayout(with:)
。这是实际用于调整截面/行高,移动受尺寸调整单元格影响的对象等的步骤。如果覆盖,则需要调用super
。 PS:这确实是一个很难的话题,我只是从头开始。您可以看一下here它变得多么复杂(但是此仓库也是一个非常丰富的学习工具)。