75

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?

cmacera
  • 1,140
  • 1
  • 10
  • 21
  • Make sure the indexPath of the UICollectionViewLayoutAttributes match the indexPath of the UICollectionViewCell – onmyway133 Aug 24 '20 at 14:06

18 Answers18

144

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()
BharathRao
  • 1,846
  • 1
  • 18
  • 28
Katrin Annuk
  • 1,472
  • 1
  • 9
  • 3
  • 3
    It helped, thank you. But does anyone know, why does it help? – Vilém Kurz Oct 31 '16 at 17:27
  • Great answer, solved an issue I've been having for a while with a new iOS 10 app using collection views. Would love to know why this is required though – Ashley Mills Dec 31 '16 at 17:26
  • 4
    It works when the collectionview row count increases. But crashes again when the row count decreases (ie. works fine when 5 columns to 10 columns but crashes when 10 columns to 5 columns). – Jay Mayu Feb 21 '17 at 04:17
  • Are you sure you're invalidating the layout every time you call reloadData? Because that's the behavior I saw prior to adding this workaround. – dgatwood Feb 21 '17 at 21:42
  • 2
    I'm experiencing the same as @JayMayu. I crash when the count drops. – Josh Hrach Apr 21 '17 at 18:10
  • @JoshHrach actually this [article](http://jaymayu.com/uicollectionview-and-dynamically-changing-layouts-on-runtime/) helped me. I had to call `self.collectionView.collectionViewLayout.invalidateLayout()` after reload data. – Jay Mayu Apr 24 '17 at 07:53
  • 4
    @JayMayu I've tried that but it still is crashing for certain view controllers in my app. Thanks for trying to help. I appreciate it. – Josh Hrach Apr 24 '17 at 18:33
  • My number of cells changed and that one line completely resolved my issue on iOS 10 - thank you so much! Awesome. – Henry Heleine May 13 '17 at 22:35
  • 3
    In my case i was using 2 collectionviews in 1 tableview cell. So assigning separate UICollectionViewFlowLayout to each one solved this strange crash. – M.Yessir May 19 '17 at 06:46
  • This just finally solved my problem, and I would love to know why - any insight? – ArielSD Nov 20 '17 at 16:36
  • Thank you so much its fixed after assigning the different layout for two different collectionview thank you so much @user1994956 – Hari Narayanan Mar 18 '18 at 08:15
  • This appears to only be an issue in iOS 10 and not iOS 11 – Josh Bernfeld Mar 29 '18 at 00:48
53

When you have custom layout, remember to clear cache (UICollectionViewLayoutAttributes) while overriding prepare func

    override func prepare() {
       super.prepare()
       cache.removeAll()
    }
Giovanni Trezzi
  • 395
  • 6
  • 18
ArturC
  • 556
  • 6
  • 10
  • Solved my problem! Thank you! – DaniSDE Feb 15 '18 at 19:09
  • Helped me with custom layout! Thanks – Codenator81 Feb 23 '18 at 09:07
  • @DaniSDE can you explain how / where I would use this ? Thank you – user6520705 Mar 25 '18 at 20:03
  • 4
    @user6520705 In your layout class you should have overriden prepare func, where you're populating [UICollectionViewLayoutAttributes] array. This array is your cache for layout attributes. Just clear it on the start of this prepare method. – ArturC Mar 26 '18 at 07:35
  • And if you have added "Pull-to-Refresh" control like me, you have to make the content height = 0, to make the UI correct. – g212gs Apr 20 '18 at 13:18
  • Solved my problem with custom layout. <3 – Ashley May 10 '18 at 03:27
  • Thanks a lot! I am using a custom collection view layout with pagination and search, clearing the cache solved my issues. I also added contentHeight = 0 after clearing the cache to reset the height. – RoxJ Jul 13 '18 at 19:09
  • Thank you so much! – Learner Dec 02 '18 at 12:58
  • I'm not seeing this cache in my custom layout anywhere? – airowe Jan 28 '19 at 20:22
  • @airowe Cache is an array of objects called `UICollectionViewLayoutAttributes`, are you creating one? Can you share your code? – ArturC Jan 29 '19 at 07:46
  • @ArturC This one worked for me, thanks: https://stackoverflow.com/a/45581964/1496693 – airowe Jan 29 '19 at 11:45
  • @airowe Good to hear that you resolved your problem! :) – ArturC Jan 29 '19 at 11:58
  • 1
    Thank you for this! I tried all the invalidateLayout suggestions from the other answers, but it didn't work. THIS fixed it. I had a `collectionView.performBatchUpdates` closure where I was deleting an item from my collection and getting the error mentioned in the question. I had been creating a cache in my `prepare` method, and removing all the items from it worked. Thank you thank you! (Using Swift 5 and iOS13) – N.W Jul 21 '20 at 15:18
  • @N.W I'm so happy that this answer could help you! :) – ArturC Jul 23 '20 at 04:49
23

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
Aximem
  • 714
  • 8
  • 27
  • Thanks, Its works for me. I am stuck in it from few days. You are awesome :) – Khushbu Desai Oct 26 '17 at 04:57
  • Hey, I am facing some issues I have two collection view One is cltnTools and another is cltnEdits. cltnEdits is appeared whenever user click on any of cell from cltnTools i am facing error with reason: 'UICollectionView recieved layout attributes for a cell with an index path that does not exist: {length = 2, path = 0 - 2} – Khushbu Desai Oct 26 '17 at 05:05
  • that issue is solve using let layout = UICollectionViewFlowLayout() cltnEdits.collectionViewLayout = layout every things works prefactly but my cltnEdits cells are scrolling vertically but i want horizontal scrolling for my both collectionvies so that i have written this line layout.scrollDirection = .horizontal for horizontal scroll, then i am facing that error again. could please help me with this? how can i resolve it. – Khushbu Desai Oct 26 '17 at 05:09
  • @KhushbuDesai I don't think the problem comes from setting layout horizontally / vertically, you might have an issue elsewhere when configuring your collectionview's layouts or reloading datas. I need more codes to find out the issue, you should open a question on stackoverflow with more informations ;) – Aximem Oct 26 '17 at 10:59
  • here is my Question https://stackoverflow.com/questions/46903770/uicollectionview-received-layout-attributes-for-a-cell-with-an-index-path-that-d?noredirect=1#comment80800301_46903770 – Khushbu Desai Oct 26 '17 at 11:04
6

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];
Andrey
  • 521
  • 6
  • 9
6

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.

m8labs
  • 3,671
  • 2
  • 30
  • 32
  • 5
    Not a good advice actually. You should not call ````layoutSubviews()```` directly. If you want to force a layout update, call the ````setNeedsLayout()```` method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the ````layoutIfNeeded()```` method. https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews – Artem Kirillov Jun 26 '18 at 11:48
  • @ArtKirillov yes, but it's hack anyway, otherwise you have this crash. I don't remember already, but probably layoutIfNeeded() did't work. – m8labs Jun 27 '18 at 16:12
4

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()
SandPiper
  • 2,816
  • 5
  • 30
  • 52
Kevin Bel
  • 134
  • 4
  • I have the same error and setting `dataSource` and `delegate` to `nil` solved the error. In my case I am using `UICollectionView` with two **different** `UICollectionViewLayout`s and I think the **recycling process** has problem with that. Thanks. – Mu Sa Feb 19 '18 at 13:09
4

Resetting UICollectionView's layout solved the problem for me:

let layout = UICollectionViewFlowLayout()
collectionView?.collectionViewLayout = layout
krlbsk
  • 1,051
  • 1
  • 13
  • 24
4

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
}
Nick Kallen
  • 97
  • 1
  • 4
3

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

jeff ayan
  • 819
  • 1
  • 13
  • 16
2

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
    }
    ...
Mol0ko
  • 2,938
  • 1
  • 18
  • 45
1

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)
Mati Bot
  • 797
  • 6
  • 13
1

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()
        }
    })
}
dimazava
  • 370
  • 1
  • 14
0

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

Trev14
  • 3,626
  • 2
  • 31
  • 40
0

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.

ullstrm
  • 9,812
  • 7
  • 52
  • 83
0

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 UICollectionViews: one for Foos and one for Bars. 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.

Heath Borders
  • 30,998
  • 16
  • 147
  • 256
0

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.

Arshad Shaik
  • 1,095
  • 12
  • 18
0

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

Gulfam Khan
  • 1,010
  • 12
  • 23
0

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.