在 UICollectionView 上调用 reloadData 后 contentSize 未更新
Posted
技术标签:
【中文标题】在 UICollectionView 上调用 reloadData 后 contentSize 未更新【英文标题】:contentSize is not updated after reloadData is called on UICollectionView 【发布时间】:2013-09-30 20:35:03 【问题描述】:有谁知道为什么在 UICollectionView 上调用 reloadData 后 contentSize 没有立即更新?
如果您需要知道 contentSize,我发现的最佳解决方法如下:
[_collectionView reloadData];
double delayInSeconds = 0.0001;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void)
// TODO: Whatever it is you want to do now that you know the contentSize.
);
显然,这是一个相当脆弱的 hack,它对 Apple 的实现做出假设,但到目前为止,它已被证明非常可靠。
是否有人对为什么会发生这种情况有任何其他解决方法或知识?我正在辩论提交雷达,因为我不明白为什么他们不能在同一个运行循环中计算 contentSize。这就是 UITableView 在其整个实现中的工作方式。
编辑:这个问题用于引用块内的 setContentOffset 方法,因为我想在我的应用程序中滚动集合视图。我删除了方法调用,因为人们的答案集中在为什么我没有在 contentSize 没有更新的原因中使用 scrollToItemAtIndexPath。
【问题讨论】:
我认为更合适的方法是使用 scrollToItemAtIndexPath:atScrollPosition:animated:。至于为什么不更新内容大小,我只能假设它是一种优化,以防止调用潜在的大量且昂贵的动态大小计算或返回不准确的信息,进而进行不必要的处理和工作。 在实际的应用程序中我确实使用了它。为简单起见,我选择提及 setContentOffset。这是一个很好的观点,即多次调用 reloadData 可能会触发昂贵的计算。太糟糕了,没有带有完成处理程序方法的 reloadData。 在访问UICollectionView
之前致电layoutIfNeeded
contentSize
【参考方案1】:
要获取重新加载后的内容大小,请尝试调用布局对象的collectionViewContentSize
。这个对我有用。
【讨论】:
一开始我很怀疑,但它也对我有用 :) 看起来对 reloadData 的调用不会立即触发集合视图以更改其渲染属性,但就像 Clement 建议的那样,您可以直接转到布局对象以立即获取此信息。 SWIFT 3:让 contentSize = self.myCollectionView.collectionViewLayout.collectionViewContentSize 这也解决了我的问题 :) 调用执行批量更新以获取内容高度并发生崩溃,但我的主要问题是内容高度尚不存在。但我的布局有适当的高度。【参考方案2】:这对我有用:
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView.collectionViewLayout prepareLayout];
【讨论】:
结合 layoutIfNeeded 对我有用。 这对我有用。我从自我调整大小的单元格中得到了这个错误,在 ios 10 及之前的 reloadData 之后 contentSize 不会更新。在 iOS 11 上我不需要这样做。 @x.y,你是如何在 iOS 11 中修复它的?它正常工作吗?【参考方案3】:编辑:我刚刚对此进行了测试,实际上,当数据更改时,我的原始解决方案将崩溃,并出现以下情况:
“无效更新:第0节中的项目数无效。更新后现有节中包含的项目数(7)必须等于更新前该节中包含的项目数(100),加上或减去从该部分插入或删除的项目数(0 插入,0 删除)加上或减去移入或移出该部分的项目数(0 移入,0 移出)。"
处理这个问题的正确方法是在数据源发生变化时计算插入、删除和移动,并在发生变化时围绕它们使用 performBatchUpdates。例如,如果将两项添加到作为数据源的数组的末尾,则代码如下:
NSArray *indexPaths = @[indexPath1, indexPath2];
[self.collectionView performBatchUpdates:^()
[self.collectionView insertItemsAtIndexPaths:indexPaths];
completion:^(BOOL finished)
// TODO: Whatever it is you want to do now that you know the contentSize.
];
以下是 Kernix 提供的我的原始答案的编辑解决方案,我认为它不能保证有效。
尝试在 UICollectionView 上执行 performBatchUpdates:completion:。您应该可以访问完成块中的更新属性。它看起来像这样:
[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^()
completion:^(BOOL finished)
// TODO: Whatever it is you want to do now that you know the contentSize.
];
【讨论】:
这看起来确实是一个可靠的解决方案。更改会产生动画,这不是我想要的,但我确信必须有一种方法可以杀死动画。 您可以使用 initialLayoutAttributesForAppearingItemAtIndexPath: 覆盖布局子类中的初始布局属性。将那里的框架设置为发生变化的单元格的框架。 我很确定这会崩溃,你应该把 reloadData 放到 performBatch 块之外。【参考方案4】:调用后
[self.collectionView reloadData];
使用这个:
[self.collectionView setNeedsLayout];
[self.collectionView layoutIfNeeded];
那么你就可以得到真正的contentSize
【讨论】:
【参考方案5】:先调用prepareLayout,然后你会得到正确的contentSize:
[self.collectionView.collectionViewLayout prepareLayout];
【讨论】:
不正确。正如 Apple 所说,默认实现什么都不做:developer.apple.com/library/prerelease/ios/documentation/UIKit/… 同意@CanPoyrazoğlu 这很有趣 - 我有一个案例,调用 invalidateLayout 后跟 reloadData 不会触发 sizeForItem: 调用。但是,在无效和重新加载之间直接调用 prepareLayout 会导致在重新加载期间重新计算大小,正如我所期望的那样......文档确实说实现是空的,但我不确定我是否完全相信这一点...... @CanPoyrazoğlu 文档说UICollectionViewLayout
的方法定义为空。在许多情况下,您将使用默认子类UICollectionViewFlowLayout
,它似乎没有空实现,以及为什么它适用于大多数情况。【参考方案6】:
在我的例子中,我有一个自定义布局类,它继承 UICollectionViewFlowLayout
并仅在 Interface Builder 中设置它。我不小心从项目中删除了该类,但 Xcode 没有给我任何错误,并且集合视图的 contentSize
返回始终为零。
我把课放回去了,它又开始工作了。
【讨论】:
以上是关于在 UICollectionView 上调用 reloadData 后 contentSize 未更新的主要内容,如果未能解决你的问题,请参考以下文章
同时在多个 UICollectionView 上调用 reloadData
在 UICollectionView 上调用 reloadData 后 contentSize 未更新
完成加载后,在 UICollectionView 中的第一个单元格上调用函数
在 UITextView 上点击时未调用 UICollectionView didSelectItemAtIndexPath