IOS10错误: UICollectionView 接收到索引路径不存在的单元格的布局属性

在 iOS10设备上运行我的应用程序,我得到了这个错误:

UICollectionView 接收到索引路径不存在的单元格的布局属性

在 iOS8和 iOS9中工作良好。我一直在研究,我发现这与使集合视图布局无效有关。我试图实施这个解决方案,但没有成功,所以我想寻求直接帮助。这是我的等级观点:

->Table view
->Each cell of table is a custom collection view [GitHub Repo][1]
->Each item of collection view has another collection view

我试图插入

    [self.collectionView.collectionViewLayout invalidateLayout];

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

两个集合视图的。

此外,我已经尝试使布局无效之前,做一个重新加载数据,不工作..。

有人能告诉我怎么走吗?

36151 次浏览

当 CollectionView 中的单元格数量发生变化时,我就遇到了这种情况。在调用 reloadData 之后,我发现我错过了无效布局。加上它之后,我再也没有经历过更多的崩溃。苹果对 iOS10中的 CollectionViews 做了一些修改。我想这就是为什么我们在旧版本上没有遇到同样的问题的原因。

这是我最后的密码:

[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];


//Swift 4.2 Version
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()

以前的答案有所帮助,但是如果使用自动调整单元格大小,它们的大小将不正确。

 UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.estimatedItemSize = CGSizeMake(60, 25);
layout.itemSize = UICollectionViewFlowLayoutAutomaticSize;
self.collectionView.collectionViewLayout = layout;

我解决这个问题的方法是

[self.collectionView reloadData];

[self.collectionView reloadSections:indexSet];

我在同一视图中使用2个 CollectionViews 也遇到了这个问题,因为我在两个集合中添加了相同的 UICollectionViewFlowLayout:

let layout = UICollectionViewFlowLayout()
collectionView1.collectionViewLayout = layout
collectionView2.collectionViewLayout = layout

当然,如果 CollectionView1数据发生变化,它会在重新加载数据时崩溃。

所以你应该这么做

let layout1 = UICollectionViewFlowLayout()
collectionView1.collectionViewLayout = layout1


let layout2 = UICollectionViewFlowLayout()
collectionView2.collectionViewLayout = layout2

并不是每次都最好重新加载 Data (您应该使用 insert tItems 和 delete teItems,甚至是 reloadSections)。 但是... 在某些情况下,它是有效的,所以,你可以这样做:

collectionView.dataSource = nil


collectionView.delegate = nil


/*... All changes here. ... */


collectionView.dataSource = self


collectionView.delegate = self


collectionView.reloadData()

@ Katrin 的回答很有帮助,但我可以通过添加一行来获得更好的结果:

collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutSubviews() // <-- here it is :)

我现在不能说我是否可以用这条线重现崩溃,但我想有一个... 所以,还不是一个银弹,但东西。

如果你想保持你的滚动位置并修复崩溃,你可以使用以下方法:

collectionView.reloadData()
let context = collectionView.collectionViewLayout.invalidationContext(forBoundsChange: collectionView.bounds)
context.contentOffsetAdjustment = CGPoint.zero
collectionView.collectionViewLayout.invalidateLayout(with: context)

当您有自定义布局时,请记住在重写 ready func 时清除缓存(UICollectionViewLayoutAttritribute)

    override func prepare() {
super.prepare()
cache.removeAll()
}

我也有这个问题。在我的例子中,有一个定制的 UICollectionViewLayout应用于集合视图,问题是它有应该显示的单元格数量的陈旧数据。当你看到 UICollectionView received layout attributes for a cell with an index path that does not exist的时候,你绝对可以检查一下

在我的例子中,我更改了 NSlayoutConstraint 常量

self.collectionViewContacts.collectionViewLayout.invalidateLayout()


UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}




self.collectionViewContacts.reloadData()

解决了问题

我通过在调用 reloadData()之前重新创建 collectionViewLayout来解决这个问题

这个问题可能与我有一个单独的 dataSource实例有关(即不是视图控制器持有 CollectionView) ,当交换数据源时,可能会出现崩溃。

这种情况也发生在我身上,但这是因为我的 UICollectionViewDataSource发生了变化,我没有调用 -[UICollectionView reloadData]。在我的例子中,我有以下数据结构:

struct Bar { let name: String }
struct Foo { let name: String; let bars: [Bar] }

我有两个 UICollectionView: 一个用于 Foo,一个用于 Bar。在我的 -collectionView:didSelectItemAtIndexPath:中,我有以下内容:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (collectionView == self.fooCollectionView) {
self.selectedFoo = self.foos[indexPath.row];
[self.barCollectionView reloadData];
self.fooCollectionView.hidden = YES;
self.barCollectionView.hidden = NO;
} else if (collectionView == self.barCollectionView) {
// do some stuff with the selected bar


self.selectedFoo = nil;
// This -reloadData call fixed my error. I thought I didn't
// need it since my UICollectionView was hidden
[self.barCollectionView reloadData];
self.barCollectionView.hidden = YES;
self.fooCollectionView.hidden = NO;
}
}

如果没有 -reloadData呼叫,当我旋转设备时,我会看到崩溃。

重置 UICollectionView的布局为我解决了这个问题:

let layout = UICollectionViewFlowLayout()
collectionView?.collectionViewLayout = layout

经过两天的时间,下面的代码解决了这个问题。

let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
collecView.collectionViewLayout = flowLayout
collecView.delegate = self
collecView.dataSource = self
collecView.reloadData()

然后系统崩溃将得到解决,但是您将在 Collectionview 单元格中发现问题,因为这些单元格是否按照您的设计进行了压缩。然后在 滚动方向行后面加一行代码

flowLayout.itemSize = CGSize(width: 92, height: 100)

使用上面的代码行,可以调整 Collectionview 单元格布局。

我希望它能帮到别人。

在我的情况下,拨打 invalidateLayout并没有阻止事故的发生。(如果集合视图中的项目数量增加了,但是如果数量减少了,那么它就不起作用了)。上下文: 我在 UITableViewCell 中有一个 UICollectionView,当表单元格被重用时,我重置 CollectionView 的委托。我不是通过使缓存失效来解决这个问题,而是通过在任何时候重置委托时重新创建布局对象,然后调用 reloadData () :

foo.dataSource = newDataSource
foo.delegate = newDelegate
foo.fixLayoutBug()
foo.reloadData()


func fixLayoutBug() {
let oldLayout = collectionViewLayout as! UICollectionViewFlowLayout
let newLayout = UICollectionViewFlowLayout()
newLayout.estimatedItemSize = oldLayout.estimatedItemSize
newLayout.footerReferenceSize = oldLayout.footerReferenceSize
newLayout.headerReferenceSize = oldLayout.headerReferenceSize
newLayout.itemSize = oldLayout.itemSize
newLayout.minimumInteritemSpacing = oldLayout.minimumInteritemSpacing
newLayout.minimumLineSpacing = oldLayout.minimumLineSpacing
newLayout.scrollDirection = oldLayout.scrollDirection
newLayout.sectionFootersPinToVisibleBounds = oldLayout.sectionFootersPinToVisibleBounds
newLayout.sectionHeadersPinToVisibleBounds = oldLayout.sectionHeadersPinToVisibleBounds
newLayout.sectionInset = oldLayout.sectionInset
newLayout.sectionInsetReference = oldLayout.sectionInsetReference
collectionViewLayout = newLayout
}

在我的例子中,我改变了 NSLayoutConstraint 的常量,上面的解决方案都不适合我。谢谢@jeff ayan,他的回答给了我一些提示。我用这个代码解决了问题

override func prepareForReuse() {
super.prepareForReuse()
imagesCollectionHeight.constant = 100 // changing collectionview height constant
imageContainerWidth.constant = 100  //changing width constant of some view
self.layoutIfNeeded() // this line did the trick for me
}

希望能帮到别人

下面这些是为我准备的:

[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];

这段代码似乎强制 CollectionView 在重新加载数据之前滚动到第一个单元格,这在我的例子中似乎是导致错误的原因。

我在使用 RxDataSourceRxCollectionViewSectionedAnimatedDataSource时遇到了问题,并通过结合两个答案解决了这个问题。当被动重新加载我的集合时,我必须使用 invalidateLayout():

    ...
.asDriver(onErrorJustReturn: [])
.do(onNext: { [weak self] _ in
DispatchQueue.main.async {
self?.collectionViewLayout.invalidateLayout()
self?.collectionView.layoutSubviews()
}
}).drive(collectionView.rx.items(dataSource: collectionController.dataSource))
.disposed(by: disposeBag)

并清除 prepare()布局方法中的 cache数组。以下是代码:

override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView,
collectionView.numberOfSections > 0,
let delegate = delegate else {
return
}
...

我在使用动画约束更改显示-隐藏集合视图时遇到了类似的问题。

self.filtersCollectionView.reloadData()
if isNeedToUpdateConstraint {
filtersCollectionViewHeightLayoutConstraint.constant = updatedHeight
UIView.animate(withDuration: 0.1, animations: {
self.view.setNeedsLayout()
}, completion: { completion in
if completion {
self.filtersCollectionView.collectionViewLayout.invalidateLayout()
}
})
}