iOS 10 bug: UICollectionView received layout attributes for a cell with an index path that does not exist
IosUicollectionviewIos10UicollectionviewlayoutIos Problem Overview
Running my app in a device with iOS 10 I get this error:
UICollectionView received layout attributes for a cell with an index path that does not exist
In iOS 8 and 9 works fine. I have been researching and I have found that is something related to invalidate the collection view layout. I tried to implement that solution with no success, so I would like to ask for direct help. This is my hierarchy view:
->Table view
->Each cell of table is a custom collection view [GitHub Repo][1]
->Each item of collection view has another collection view
What I have tried is to insert
[self.collectionView.collectionViewLayout invalidateLayout];
In the
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
of both collection views.
Also I have tried to invalidate layout before doing a reload data, does not work...
Could anyone give me some directions to take?
Ios Solutions
Solution 1 - Ios
This happened to me when number of cells in collectionView changed. Turns out I was missing invalidateLayout after calling reloadData. After adding it, I haven't experienced any more crashes. Apple has made some modifications to collectionViews in iOS10. I guess that's the reason why we are not experiencing same problem on older versions.
Here's my final code:
[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];
//Swift 4.2 Version
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
Solution 2 - Ios
When you have custom layout, remember to clear cache (UICollectionViewLayoutAttributes) while overriding prepare func
override func prepare() {
super.prepare()
cache.removeAll()
}
Solution 3 - Ios
I also faced this issue with 2 collectionViews in the same view because I added the same UICollectionViewFlowLayout in both collections :
let layout = UICollectionViewFlowLayout()
collectionView1.collectionViewLayout = layout
collectionView2.collectionViewLayout = layout
Of course it crashes on reload data if collectionView1 Data change. If this could help someone.
So you should do this instead
let layout1 = UICollectionViewFlowLayout()
collectionView1.collectionViewLayout = layout1
let layout2 = UICollectionViewFlowLayout()
collectionView2.collectionViewLayout = layout2
Solution 4 - Ios
Previous answer helps, but if you use autoresizing cells, their size will be incorrect.
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.estimatedItemSize = CGSizeMake(60, 25);
layout.itemSize = UICollectionViewFlowLayoutAutomaticSize;
self.collectionView.collectionViewLayout = layout;
I solve this issues by replacing
[self.collectionView reloadData];
to
[self.collectionView reloadSections:indexSet];
Solution 5 - Ios
The @Katrin's answer helped a lot, but I could achieve even better results by adding one more line:
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutSubviews() // <-- here it is :)
I can't now say if I could reproduce crash with this line or not, but I guess there was one... So, still not a silver bullet, but something.
Solution 6 - Ios
It's not the best to reloadData everytime (You should use insertItems and deleteItems, and even reloadSections). But... after saying that in some cases it's a valid so, you can actually do this:
collectionView.dataSource = nil
collectionView.delegate = nil
/*... All changes here. ... */
collectionView.dataSource = self
collectionView.delegate = self
collectionView.reloadData()
Solution 7 - Ios
Resetting UICollectionView
's layout solved the problem for me:
let layout = UICollectionViewFlowLayout()
collectionView?.collectionViewLayout = layout
Solution 8 - Ios
Calling invalidateLayout
did not prevent the crash in my case. (It worked if the number of items in the collection view increased but not if it decreased). Context: I have a UICollectionView inside a UITableViewCell and when the table cell is re-used I reset the delegates of the collectionView. I fixed the problem not by invalidating the cache but by RECREATING the layout object any time I reset the delegate, then calling 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
}
Solution 9 - Ios
In my case, I was changing the NSlayoutConstraint constant
self.collectionViewContacts.collectionViewLayout.invalidateLayout()
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
self.collectionViewContacts.reloadData()
solved the issue
Solution 10 - Ios
I had the problem using RxDataSources with RxCollectionViewSectionedAnimatedDataSource
and solved it by combining two answers. I have to invalidateLayout()
when reactively reload my collection:
...
.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)
and clear cache
array(s) in prepare()
layout method. Here is the code:
override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView,
collectionView.numberOfSections > 0,
let delegate = delegate else {
return
}
...
Solution 11 - Ios
If you want to keep your scroll position and fix the crash you can use this:
collectionView.reloadData()
let context = collectionView.collectionViewLayout.invalidationContext(forBoundsChange: collectionView.bounds)
context.contentOffsetAdjustment = CGPoint.zero
collectionView.collectionViewLayout.invalidateLayout(with: context)
Solution 12 - Ios
I faced the similar issue while showing-hiding collection view using animated constraints change. My solution is based on existing answers.
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()
}
})
}
Solution 13 - Ios
I had this problem as well. In my case there was a custom UICollectionViewLayout
applied to the collection view & the problem was that it had stale data for the number of cells that should be displayed. That's definitely something you can check on when you see UICollectionView received layout attributes for a cell with an index path that does not exist
Solution 14 - Ios
I managed to solve this by recreating the collectionViewLayout
before calling reloadData()
The issue was probably related to me having a separate instance for the dataSource
(i.e. not the view controller holding the collectionView), and when swapping datasource, the crash could appear.
Solution 15 - Ios
This happened to me as well, but it was because my UICollectionViewDataSource
changed, and I didn't call -[UICollectionView reloadData]
. In my case, I had the following data structure:
struct Bar { let name: String }
struct Foo { let name: String; let bars: [Bar] }
I had two UICollectionView
s: one for Foo
s and one for Bar
s. In my -collectionView:didSelectItemAtIndexPath:
, I had the following:
- (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;
}
}
Without the -reloadData
call, I would see the crash when I rotated the device.
Solution 16 - Ios
After spending two days of time, the below code solved the issue. Before loading a collectionview write the below code.
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
collecView.collectionViewLayout = flowLayout
collecView.delegate = self
collecView.dataSource = self
collecView.reloadData()
Then the crash will be resolved, but you will find issue in collectionview cells, as the cells are compressed or not as per your design. Then just a line of code after scrolldirection line
flowLayout.itemSize = CGSize(width: 92, height: 100)
With the above line of code, you can adjust you collectionview cell layout.
I hope it will help someone.
Solution 17 - Ios
In my case I was changing the constant of NSLayoutConstraint none of the above solution worked for me. thanks to @jeff ayan whose answer gave me the hint. I solved the problem with this code
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
}
Hope it helps someone
Solution 18 - Ios
The following made it for me:
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
This code seems to force collectionView to scroll to the first cell before reloading data, what seems to cause the error in my case.