35

I'm working on an app that has a UICollectionViewController that is crashing in certain mysterious situations that are hard to reproduce. The log for the crash looks like this:

*** Assertion failure in -[UICollectionViewData validateLayoutInRect:], /SourceCache/UIKit_Sim/UIKit-3318.16.14/UICollectionViewData.m:417
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}'

Crashes like this only seem to have started occurring in our code once we switched to the iOS 8 SDK.

Why is this happening?

Note: I already know what the answer to question is, but I found very little information relating to this crash on Stack Overflow and the rest of the web. I'll post the answer below. This bug took my coworker and I three days to track down, so hopefully this post will save someone else a lot of time and frustration. I have filed this bug with Apple.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Drew
  • 926
  • 1
  • 9
  • 13
  • In my case the crash was being caused by setting the contentInset property directly on the collectionView. Once I moved the insets into the flowLayout (.sectionInset) the crash was gone... – Rygen Feb 24 '17 at 11:22

10 Answers10

30

The crash was happening in the following situation:

We had a collection view controller that was presenting another view controller on top of it.

While the collection view controller was no longer visible, the following sequence of events was occasionally occurring in response to our app's back end requests.

  1. [UICollectionView insertItemsAtIndexPaths:] was called with 50 items on the collection view of the hidden UICollectionViewController.
  2. [UICollectionView reloadData] was called on the hidden collection view.
  3. A short delay would occur.
  4. The number of items in the hidden collection view was set to a small number.
  5. [UICollectionView reloadData] was called again.
  6. The view controller was dismissed, revealing the hidden collection view controller.

The assertion failure in the internal UIKit class UICollectionViewData would happen at step 6.

So, the lesson is, try to avoid manipulating a collection view that is not visible on the screen.

Our workaround for this problem was to call [UICollectionView reloadSections:] instead of [UICollectionView reloadData] at key points.

We suspect that the effects of reloadData are deferred to some point in the future, and there are consequently subtle issues with how this may interact with other method calls like insertItemsAtIndexPaths, whereas reloadSections is handled immediately, leaving the collection view in a better state.

We think that we were not seeing this behavior until we started building our app for iOS 8.

Sleep well, my friends!

Fabio Berger
  • 1,921
  • 2
  • 24
  • 29
Drew
  • 926
  • 1
  • 9
  • 13
  • Haven't seen such problem. On iOS 7 if window = nil, u simply run reloaData to avoid crash. – pronebird Dec 17 '14 at 02:01
  • Thanks, Andy. I want to understand what you are suggesting exactly. Do you mean that when collectionView.window == nil, we should always call reloadData instead of insertItemsAtIndexPaths? – Drew Dec 17 '14 at 21:37
  • 1
    Yes, if view.window = nil, it means view is offscreen. Collection views had issues on iOS 7 when offscreen but reloadData worked. I am not sure about iOS 8 though. – pronebird Dec 17 '14 at 23:42
  • Thanks for clarifying, Andy. That is really helpful. We will try limiting updates to reloadData when the collection view is offscreen. – Drew Dec 19 '14 at 00:29
  • Same thing happened to me. I did an animated popViewController, and without waiting for the animation to finish, did reloadData. This causes a crash on 7.x with a line like "QuartzCore 0x016315b0 +[CATransaction flush]" in the stacktrace. – Bart van Kuik Apr 30 '15 at 06:47
  • I encountered this same issue; however, it didn't seem to have anything to do with being 'onscreen'. In my case, I was able to solve the problem simply by invalidating the layout before making a call to reloadData or anything adjusting the content offset (and as a resulting touching cached layout info) of the collectionview – Sean Danzeiser Aug 13 '15 at 06:34
  • 3
    For me was the same as Sean Danzeiser. I was modifying constraints in the `willSet` observer of the cell counter and then was calling the `layoutIfNeeded` inside `UIView.animateWithDuration` method. It worked the first round of adding/removing cells, but when the 2nd collectionView went empty and then tried to populate it again with data, the `layoutIfNeeded` crashed. **The solution was to invalidate the layout just before the layoutIfNeeded call**. – carlos_ms Jun 22 '16 at 20:31
  • Your problem sounds familiar. I had this exact problem and still have not figured out a solution. I have managed to make the application stop crashing but the problems is there, ticking. I am currently developing another way to do that. If your problem is like mine, your solution will fail on you very soon. You will discover that you will still have crashes from time to time. Unfortunately the thing is poorly designed. – Duck Dec 08 '16 at 23:22
  • 1
    I owe you a gift for Xmas. – Jean Le Moignan Dec 09 '16 at 20:40
  • I owe you a bunch of gifts! – djserva Feb 15 '17 at 17:46
23

For me it was the UICollectionViewLayoutAttribute's array. I'm using it in a UICollectionViewLayout to store item's attribute.

I forgot to empty it in the prepareLayout method.

So the layoutAttributesForItemAtIndexPath was returning incorrect values for indexPath, which led to the same crash.

With just a removeAll on the array at the beginning of prepareLayout, it's working.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Beuj
  • 602
  • 6
  • 20
8

collectionViewLayout caches the attributes. In viewwillappear - Create a new instance of collectionViewLayout and assign it to collectionview.collectionViewLayout In this way all the cached attributes will purge before the reload Your problem might be resolved. Worked for me, especially when you are using other collectionViewLayout libraries.

Ankish Jain
  • 11,305
  • 5
  • 36
  • 34
  • 1
    I was using the same collectionViewLayout for three different collection views. As soon as I used a separate collectionViewLayout for each one, the bug went away, thanks! – NYC Tech Engineer Dec 03 '15 at 20:58
  • Separate flow layouts for the win! I had two collectionViews sharing the same flow layout since they were nearly identical in shape and form. Creating a fully new instance for the 2nd one fixed this odd issue! – Miro Dec 05 '15 at 21:58
  • After trying almost everything else, this finally seems to have fixed the issue for me. Thank you. – userx Feb 21 '19 at 03:46
6

I have been working on this same bug for some time and think I've found another source of errors, on iOS 7 but working ok on iOS 8, that causes this same exact error: Auto-Layout!

I'm using a control that has embedded UICollectionViews in it, a grid. I've noticed by hacking the code to remove the UICollectionViewLayoutAttributes when there is a mismatch with the UICollectionView data, causing the crash, that other controls in my view were misplaced.

My collection content is "static", loaded on load and that's it so no possibility for unprotected changes. Again this works perfectly with iOS 8.

So, i disabled the Auto-Layout feature for this view only and BINGO! Crash disappeared. The Auto-Layout was playing with the internal UICollectionSize, thus the -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect method was returning mixup results.

I tried reloadData, reloadSections, nothing does but this works!

Hope it will help someone else fighting with UIKit exception with this control.

Eric Giguere
  • 766
  • 5
  • 9
5

try calling layoutIfNeeded()

 cell.collectionView.collectionViewLayout.invalidateLayout()

 cell.collectionView.layoutIfNeeded()

 cell.collectionView.reloadData()
vrat2801
  • 538
  • 2
  • 13
Gulz
  • 1,773
  • 19
  • 15
5

Xcode 8 - Swift 3

In my case this bug was caused by Autolayout; I have a collectionView embedded into a UIView that I hide by setting it's height to 0 when the collectionView is empty and back to 150 when I need to show the collectionView again.

I managed to remove the bug by calling

collectionView.collectionViewLayout.invalitdateLayout()

before I run the code that animates the layoutIfNeeded() call on the superView. It's pretty smooth now.

I hope it can help someone in the future.

var sponsoredPlaceSummaries: [PlaceSummary] = [] {

        didSet {

            if sponsoredPlaceSummaries.isEmpty {

                self.sponsoredPlacesViewHeight.constant = 0
                self.collectionView.collectionViewLayout.invalidateLayout()

                UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseInOut, animations: {
                    self.view.layoutIfNeeded()

                }, completion: nil)

            } else if self.sponsoredPlacesViewHeight.constant != 150 {

                self.sponsoredPlacesViewHeight.constant = 150

                UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseInOut, animations: {
                    self.view.layoutIfNeeded()

                }, completion: nil)
            }
        } 
Edouard Barbier
  • 1,815
  • 2
  • 19
  • 32
1

I met the same problem. In my situation, I have 2 UICollectionView in an screen and they have the same size.

So I reuse the same UICollectionViewLayout as initial parameter <-- That's the problem.

2 UICollectionView should use different UICollectionViewLayout parameter. So just new another UICollectionViewLayout for second UICollectionView.

timyau
  • 812
  • 1
  • 16
  • 27
1

For me, it was something related to @Drew's answer (my collection view was off screen). I was manipulating a height constraint of the collectionView to collapse it when there is no data. It caused this crash (sometimes!). I put collectionView inside another view and re-assigned @IBOutlet to this new view's height constraint. And made the height of the collectionView to be constant. Crash has gone forever!

m8labs
  • 3,671
  • 2
  • 30
  • 32
0

Make sure your datasource & delegates for collectionview are connected to correct view controller. I once got this crash ,as i accidently discarded changes in my Main.Storyboard & due to which data source & delegates left disconnected

vrat2801
  • 538
  • 2
  • 13
0

I was getting the same error, but it was happening in a pretty random fashion, as in I could not recreate the bug consistently. I was using a customLayout where I was setting the attributes for items manually. I would do one thing on my real iPhone that would crash it, but on the xCode simulator it would work. What ended up fixing my problem was using collectionView.reloadData() instead of collectionView.reloadSections([mySectionNum]). I have no idea why it was working sometimes and not others. It seems like it should have crashed all the time, but it was not. Nor, do I have any idea why this fix works.

Daniel Jones
  • 1,032
  • 10
  • 18
  • I am facing the same issue. Can't able to reproduce the bug same way. Its random. also, I am not even using **collectionView.reloadSections([mySectionNum])**. I am using **collectionView.reloadData()** everywhere. Any suggestions ?? – iUser Oct 02 '17 at 19:10