使用 UICollectionViewLayoutInvalidationContext 优化插入、删除和移动过程中的布局计算

Mus*_*ang 6 ios uicollectionview uicollectionviewlayout

我已经UICollectionViewLayout为我实现了一个自定义UICollectionView,也是一个UICollectionViewLayoutInvalidationContext希望获得良好性能的自定义。

在我的第一个用例中,我有浮动标题,在滚动期间我只使需要滑动的标题无效。

我这样做如下:

  • YESshouldInvalidateLayoutForBoundsChange:.
  • 当我的invalidationContextForBoundsChange:方法被调用时,我通过调用invalidateSupplementaryElementsOfKind:atIndexPaths:.
  • invalidateLayoutWithContext:被调用时,我真的不需要做任何事情,因为使标头无效足以使以下关键事情发生layoutAttributesForSupplementaryViewOfKind:atIndexPath:被调用而不是从prepareLayout.

这很好用。

我的第一个问题是:这个场景如何抑制prepareLayout序列?起初我认为向上下文添加任何数据(在这种情况下是陈旧补充的索引路径)足以让 Apple 假设我知道我在做什么(iOS8-wise)而不是调用prepareLayout. 我知道不再相信这一点,正如您将在下面看到的那样。

我的第二个用例:在实践中,我的数据源几乎总是只在最后添加项目。对于我的布局算法,这意味着较早(indexPath-wise)项目的布局不会因新数据而失效。我不想花时间重新计算我不需要的潜在大量布局属性。因此,当客户端代码调用时,我需要做“正确的事情”:

[collection insertItemsAtIndexPaths: newIndices];
Run Code Online (Sandbox Code Playgroud)

通过注意最早的 indexPath 是什么newIndices并从那里开始重新计算我的布局。几乎总是newIndices从最后开始,并且没有现有的单元格变得陈旧——因此 Apple 应该只询问我的布局对象的新单元格的属性。

但请注意,invalidationContextForBoundsChange:对于插入/删除/移动调用没有类似的方法。所以我无法预先配置我的上下文,并说明什么索引来来去去。

所以在调用内部insertItemsAtIndexPaths:会发生什么:

  • 我的上下文对象被初始化。这发生得早——在invalidateLayout被调用之前。在初始化时,self.invalidateEverything并且self.invalidateDataSourceCounts是 (NO,NO) (并且它们是只读的)。
  • 接下来,我invalidateLayoutWithContext:现在被调用,invalidateDataSourceCounts设置为YES--Apple 只告诉我我的计数已经过时。这是没用的,因为它没有告诉我关于在哪里重新开始布局计算的任何信息。但这是我第一次(也是唯一一次)注意到某事或做某事。
  • 接下来我prepareLayout被叫到——Grrr!我不想重新开始。我不想prepareLayout打电话——或者至少我需要在它被调用之前隐藏一些信息,这样我就不会过度工作。但是我唯一的机会是在,invalidateLayoutWithContext:并且在调用它时我什么都不知道(请记住没有类似于invalidationContextForBoundsChange:)。进一步invalidateDataSourceCounts==YES是没有用的。

我尝试invalidateItemsAtIndexPaths:查看内部的任何旧单元格,invalidateLayoutWithContext:以测试这是否足以阻止调用prepareLayout(以防我确实以某种方式确定了哪些单元格要失效)。不行——prepareLayout还是被叫了。那么第二个问题:对苹果invalidateLayoutWithContext:这样invalidateItemsAtIndexPaths:的事情提出反应是否为时已晚?

现在雪上加霜的是,我在调试器中注意到,invalidateLayoutWithContext:当时我的上下文有一个名为的私有 ivar _updateItems,其中包含我需要的所有信息(只有在我有机会使用它的时候)。但是这个 ivar 没有吸气剂。这些UICollectionViewUpdateItem条目仅在我的prepareForCollectionViewUpdates:方法被调用时传递。但是猜猜这什么时候被调用 - 在prepareLayout和朋友(collectionViewContentSizelayoutAttributesForElementsInRect:)之后。

我要么丢失了某些关键内容,要么这些 API 已损坏。当然,我应该能够有效地在布局末尾添加一个单元格。我是不是应该注意到invalidateDataSourceCounts==YES并警告我的prepareLayout实现——“对不起,你被叫到了,先别做任何事情。我会在我知道该怎么做后自己再给你打电话。”

或者客户端代码应该以某种方式创建我的上下文实例并调用之前配置它[collection insertItemsAtIndexPaths: newIndices]?但是,客户应该如何在insertItemsAtIndexPaths:调用中传递这个上下文呢?此外,客户真的应该接触到这个实现细节吗?