UICollectionViewFlowLayout子类在滚动和重新加载时崩溃访问数组

我有一个UICollectionView使用UICollectionViewFlowLayout的自定义子类,使节标题粘贴到屏幕的顶部,而滚动就像UITableView的普通风格。 我的代码是基于这种方法:

http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using

此外,收集视图设置为当用户滚动到底部时加载更多结果。 也就是说,当用户到达集合视图的底部,一个Web请求加载更多的数据,然后重新加载集合视图,即调用reloadData。

这似乎工作得很好,除了我已经通过testing运行iOS 7和7.1的TestFlight从testing版testing人员那里得到了一些崩溃报告,他们说,当他们向下滚动到底部(即触发更多的结果加载)时偶尔会发生以下情况:

*** -[__NSArrayM objectAtIndex:]: index 28 beyond bounds [0 .. 16] PRIMARY THREAD THREAD 0 __exceptionPreprocess objc_exception_throw -[__NSArrayM objectAtIndex:] -[UICollectionViewFlowLayout(Internal) _frameForItemAtSection:andRow:usingData:] -[UICollectionViewFlowLayout layoutAttributesForItemAtIndexPath:usingData:] -[UICollectionViewFlowLayout layoutAttributesForItemAtIndexPath:] -[MyCustomCollectionViewFlowLayout layoutAttributesForItemAtIndexPath:] -[MyCustomCollectionViewFlowLayout layoutAttributesForSupplementaryViewOfKind:atIndexPath:] -[MyCustomCollectionViewFlowLayout layoutAttributesForElementsInRect:] -[UICollectionViewData validateLayoutInRect:]_block_invoke -[UICollectionViewData validateLayoutInRect:] -[UICollectionView layoutSubviews] -[UIView(CALayerDelegate) layoutSublayersOfLayer:] -[CALayer layoutSublayers] 

看起来好像当我的自定义stream布局代码调用[self.collectionView numberOfItemsInSection:someSection]来获取节的最后一项的布局属性时,该调用基于新加载的数据返回(例如,在这种情况下, 29项),但默认stream布局的内部仍然使用某种caching的数据(例如,在这种情况下,该部分只有17项)。 不幸的是,我不能自己再现崩溃,甚至有经验的testing者也不能一致地重现崩溃。

任何想法发生了什么?

编辑2 ,按照BenRB的评论。

当dataSource得到更新并调用reloadData时,后者确实使CV中的所有内容无效。 然而,启动的刷新过程的逻辑和确切的顺序发生在默认的stream程布局中,并且对我们来说是隐藏的。

特别是,默认的stream布局有自己的_prepareLayout (准确地说,有下划线)方法,它独立于prepareLayout及其子类提供的重载。

prepareLayout (不带下划线)基础stream布局类的默认实现顺便说一句,

在刷新过程中,默认stream布局通过layoutAttributesForElementsInRect:layoutAttributesForItemAtIndexPath: “callbacks”为其子类提供了提供更多信息的机会(例如,额外的layoutAttributes)。 为了保证基类数据和各自的indexPath / layoutAttributes数组之间的一致性,只能在这些方法中调用相应的“super”

  • [super layoutAttributesForElementsInRect:]只在重载[layoutAttributesForElementsInRect:]

  • [super layoutAttributesForItemAtIndexPath:]只在重载[layoutAttributesForItemAtIndexPath:]

这些方法之间不应该发生任何交叉调用,至lessindexPaths不是由它们相应的“super”方法提供的,因为我们不知道里面发生了什么

我和我的简历长时间打架,最终以唯一的工作顺序结束:

  1. 通过直接访问dataSource(不需要CV的numberOfItemsInSection :)的调解来准备add'l布局数据,并将该数据存储在子类的对象内,例如在字典属性中,使用indexPath作为关键字。 我正在重载[prepareLayout]

  2. 当通过callback请求这些信息时,将存储的布局数据提供给基类​​:

// layoutAttributesForElementsInRect

 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { //calls super to get the array of layoutAttributes initialised by the base class **NSArray *array = [super layoutAttributesForElementsInRect:rect];** for(MyLayoutAttributes *la in array) if(la.representedElementCategory == UICollectionElementCategoryCell ){ NSIndexPath indexPath = la.indexPath //only this indexPath can be used during the call to layoutAttributesForItemAtIndexPath:!!! //extracts custom layout data from a layouts dictionary MyLayoutAttributes *cellLayout = layouts[la.indexPath]; //sets additional properties la.this = cellLayout.this la.that = cellLayout.that ... } .... return array; } 

// layoutAttributesForItemAtIndexPath:

 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { MyLayoutAttributes *la = (MyLayoutAttributes *)[super layoutAttributesForItemAtIndexPath:indexPath ]; if(la.representedElementCategory == UICollectionElementCategoryCell ){ NSIndexPath indexPath = la.indexPath //only this indexPath can be used during the call !!! //extracts custom layout data from a layouts dictionary using indexPath as a key MyLayoutAttributes *cellLayout = layouts[la.indexPath]; //sets additional properties la.this = cellLayout.this la.that = cellLayout.that } return la; } 

我现在正在看到类似的崩溃,即布局与数据源不同步的地方。 这一个我可以重现,我相信我find了原因。 既然它是相似的,我想知道是否是我原来的崩溃/问题是一样的原因。 错误是:

UICollectionView接收到索引path不存在的单元格的[sic]布局属性

我的UICollectionViewFlowLayout子类实现了shouldInvalidateLayoutForBoundsChange:这样我可以在用户滚动时将节标题定位在可见边界的顶部)。 如果发生边界更改,并且该方法返回YES ,则系统请求轻量布局无效,即传递给布局的无效上下文的所有属性都为NOinvalidateEverythinginvalidateDataSourceCountsinvalidateFlowLayoutAttributesinvalidateFlowLayoutDelegateMetrics 。 如果reloadData之后被调用(大概在布局被重新处理之前),那么系统将不会自动调用invalidateLayout ,也就是说,当所有这些属性都为YES ,它不会触发重量级的无效上下文。 所以当这种情况发生的时候,布局显然不会更新内部caching的部分/项目计数等,导致布局与数据源和崩溃不同步。

所以对于刚刚遇到的特定情况,我可以在调用reloadData之后简单地通过调用invalidateLayout来解决这个问题。 我不确定这是否与我原来的问题/崩溃情况相同,但听起来确实如此。

我有同样的问题。 我修好了

 __weak typeof(self) weakSelf = self; UICollectionView *collectionView = self.collectionView; [UIView performWithoutAnimation:^{ [collectionView performBatchUpdates:^{ collectionView.collectionViewLayout = layout; } completion:^(BOOL finished) { __strong typeof(self) strongSelf = weakSelf; if (strongSelf){ [strongSelf reloadThumbData]; } }]; }]; 

我有同样的问题,虽然在我的情况下自定义布局。 我的解决scheme是打电话给:

  • insertItems
  • deleteItems
  • updateItems

在适当的collectionView上,以确保dataSource和布局知道相同数量的项目。

我的布局是基于从下面返回的数据:

 super.layoutAttributesForElements(in: rect) 

这并没有返回数据源指定的项目的预期数量。

insertItems的文档似乎证实了这一点:

 Call this method to insert one or more new items into the collection view. You might do this when your data source object receives data for new items or in response to user interactions with the collection view. The collection view gets the layout information for the new cells as part of calling this method.