21

Assertion Failure in UICollectionViewData validateLayoutInRect on iOS7.

I am trying to delete all UICollectionView items, one by one, using a for loop; I posted my code below. I delete the UICollectionView items using deleteItemsAtIndexPaths. It's working perfectly on iOS6, but crashes in iOS7 with this exception:

Assertion Failure in UICollectionViewData validateLayoutInRect

I delete the object from collectionArray then self.collectionView, one by one, using indexPath. When I delete the 4th object its raises Assertion failure on iOS7. Here I am using performBatchUpdates.

Please help me get the proper result in iOS7. Share proper code. Thanks in advance.

try  {    
    for (int i=count-1; i>=0; i--)  {  
        [self.collectionView performBatchUpdates:^(void){  
            [collectionArray removeObjectAtIndex:i]; // First delete the item from you model   
            [self.collectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForRow:i inSection:0]]];  
        } completion:nil];
        [self.collectionView reloadData];
    }
}
@catch (NSException *exception) {
}
@finally {
}
James Nelson
  • 1,793
  • 1
  • 20
  • 25
user1888996
  • 271
  • 1
  • 3
  • 6
  • looks like you have the same problem as mentioned in: http://stackoverflow.com/questions/18189311/bad-access-on-uicollectionview-setcollectionviewlayoutanimated and http://stackoverflow.com/questions/18339030/uicollectionview-assertion-error-on-stale-data – Ivan Lisovyi Oct 18 '13 at 07:42
  • @IvanLisovyi The first one is a crash when sending a message to an object whereas this here is an exception thrown by a failing assertion. No relationship whatsoever. The 2nd one may be related but I cannot say for sure as it's not said here what exception is thrown exactly. – Mecki Dec 20 '18 at 10:43

8 Answers8

24

I actually got this crash one time not because I was returning zero for a number of sections or items in a section but because I was reusing a flow layout like this for more than 1 collection view:

UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
Collection1 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 50.0f) collectionViewLayout:flowLayout];
[Collection1 setDataSource:self];
[Collection1 setDelegate:self];
[self.view addSubview:Collection1];

Collection2 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, self.view.frame.size.height) collectionViewLayout:flowLayout];
Collection2.backgroundColor = [UIColor whiteColor];

Instead if I create a new flow layout for each UICollectionView I avoid this crash. Hopefully that might help someone

UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
Collection1 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 50.0f) collectionViewLayout:flowLayout];
[Collection1 setDataSource:self];
[Collection1 setDelegate:self];
[self.view Collection1];

UICollectionViewFlowLayout *flowLayoutVert = [[UICollectionViewFlowLayout alloc] init];
[flowLayoutVert setScrollDirection:UICollectionViewScrollDirectionVertical];
Collection2 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, self.view.frame.size.height) collectionViewLayout:flowLayoutVert];
Chris Klingler
  • 5,258
  • 2
  • 37
  • 43
20

in iOS 10 you must disable the prefetchingEnabled:

// Swift
if #available(iOS 10, *) { 
    collectionView.prefetchingEnabled = false 
}

//Obj C
if ([self.collectionView respondsToSelector:@selector(setPrefetchingEnabled:)]) {
    self.collectionView.prefetchingEnabled = false;
}
dang
  • 1,549
  • 1
  • 20
  • 25
4

It looks like you probably want to do this:

[self.CollectionView performBatchUpdates:^(void) {
  for (int i = count - 1; i >= 0; i--) {
    [collectionArray removeObjectAtIndex:i]; // First delete the item from you model
    [self.CollectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForRow:i inSection:0]]];
  }
} completion:nil];

So that all the updates are performed together. Otherwise you will end up trying to perform several lots of batch updates on top of each other.

James Snook
  • 4,063
  • 2
  • 18
  • 30
4

layout lifecycle

You should implement invalidateLayout in your layoutClass and remove all kinds of UICollectionViewLayoutAttributes items in your config.

- (void)invalidateLayout{
  [super invalidateLayout];
  [self.itemsAttributes removeAllObjects];
}
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
Zev003
  • 85
  • 4
3

try to call [yourCollectionView.collectionViewLayout invalidateLayout];

pkamb
  • 33,281
  • 23
  • 160
  • 191
Ash
  • 5,525
  • 1
  • 40
  • 34
2

You should implement invalidateLayout in your layout class and remove all kinds of UICollectionViewLayoutAttributes items in your config.

- (void)invalidateLayout{
  [super invalidateLayout];
  [self.itemsAttributes removeAllObjects];
}

For a better way to implement invalidateLayoutWithContext, see more about UICollectionViewLayoutInvalidationContext.

From Apple Developer documentation:

When implementing a custom layout, you can override this method and use it to process information provided by a custom invalidation context. You are not required to provide a custom invalidation context but might do so if you are able to provide additional properties that can help optimize layout updates. If you override this method, you must call super at some point in your implementation.

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
Zev003
  • 85
  • 4
0

In my case following delegate was missing:

func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
Venu Gopal Tewari
  • 5,672
  • 42
  • 41
-2
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    [collectionView.collectionViewLayout invalidateLayout];
    return 1;
}
HiDeoo
  • 10,353
  • 8
  • 47
  • 47
chan li
  • 1
  • 1