iOS 10 个错误:UICollectionView 接收到具有不存在的索引路径的单元格的布局属性

iOS 10 bug: UICollectionView received layout attributes for a cell with an index path that does not exist

运行 我的应用程序在 iOS 10 的设备上 我收到此错误:

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

在 iOS 中,8 和 9 工作正常。我一直在研究,发现这与使集合视图布局无效有关。我试图实施该解决方案但没有成功,所以我想寻求直接帮助。这是我的层次结构视图:

->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

两个集合视图。

我也曾尝试在重新加载数据之前使布局无效,但没有用...

谁能给我一些指示?

当 collectionView 中的单元格数量发生变化时,这发生在我身上。事实证明我在调用 reloadData 后缺少 invalidateLayout。添加后,我没有再遇到任何崩溃。 Apple 在 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 个 collectionView 的问题,因为我在两个集合中添加了相同的 UICollectionViewFlowLayout :

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

当然,如果 collectionView1 数据发生变化,它会在重新加载数据时崩溃。如果这可以帮助某人。

所以你应该这样做

let layout1 = UICollectionViewFlowLayout()
collectionView1.collectionViewLayout = layout1

let layout2 = UICollectionViewFlowLayout()
collectionView2.collectionViewLayout = layout2

每次都reloadData不是最好的(你应该使用insertItems和deleteItems,甚至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)

当你有自定义布局时,记得在覆盖 prepare func 时清除缓存 (UICollectionViewLayoutAttributes)

    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

经过两天的时间,下面的代码解决了这个问题。在加载 collectionview 之前写下下面的代码。

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

然后崩溃将得到解决,但您会在 collectionview 单元格中发现问题,因为单元格是否按照您的设计进行了压缩。然后在 scrolldirection line

之后的一行代码
flowLayout.itemSize = CGSize(width: 92, height: 100)

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

希望对大家有所帮助。

调用 invalidateLayout 并没有阻止我的崩溃。 (如果集合视图中的项目数量增加则有效,但如果减少则无效)。上下文:我在 UITableViewCell 中有一个 UICollectionView,当重新使用 table 单元格时,我重置了 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 none 的常量对我有用。感谢@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 在重新加载数据之前滚动到第一个单元格,在我的案例中似乎是导致错误的原因。

我在使用 RxDataSourcesRxCollectionViewSectionedAnimatedDataSource 时遇到问题,并通过结合两个答案解决了这个问题。当反应性重新加载我的 collection:

时,我必须 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()
        }
    })
}