14

Using IOS 6 and the new UICollectionView I have 2 UICollectionViews on the UIViewController and using a Storyboard I get the following error when I click around the UICollectionView I'm not sure why this is happening and Google returns 0 results for this error...

2012-10-13 20:30:06.421 MyApp[768:907] Assertion failure in -[UICollectionViewData numberOfItemsBeforeSection:], /SourceCache/UIKit/UIKit-2372/UICollectionViewData.m:415
2012-10-13 20:30:06.427 MyApp[768:907] Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for number of items before section 2147483647 when there are only 1 sections in the collection view'

I'm not using Sections at all and have it set to just 1 section:

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

It happens (appears to be randomly) when I click on on UICollectionView area I have a piece of code to remove element from the UICollectionView:

[tilesArray removeObjectAtIndex:indexPath.row];
[self.tilesCollectionView reloadData];        
[resultsArray addObject:str];
[self.resultsCollectionView reloadData];

Here is how the arrays hooked up to the UICollectionViews

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {

    if(collectionView.tag == 2)
        return [resultsArray count];
    else
        return [tilesArray count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    if(collectionView.tag == 2)
    {
        static NSString *cellIdentifier = @"ResultCell";
        ResultCell *cell = (ResultCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];

        cell.mainImage.image = [UIImage imageNamed:currentArtContainer.tileMainImage];

        NSString *cellData = [resultsArray objectAtIndex:indexPath.row];
        [cell.titleLabel setText:cellData];

        return cell;
    }
    else
    {
        static NSString *cellIdentifier = @"TileCell";
        TileCell *cell = (TileCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];

        cell.topImage.image = [UIImage imageNamed:currentArtContainer.tileTopImage];

        cell.mainImage.image = [UIImage imageNamed:currentArtContainer.tileMainImage];

        NSString *cellData = [tilesArray objectAtIndex:indexPath.row];
        [cell.titleLabel setText:cellData];

        return cell;
    }
}

Please help me resolve this issue. Not even sure how I can catch the exception since it's coming from the UICollectionView internal code. Also forgot to mention I'm using a custom layout for the UICollectionView but I don't know if it's related to this issue.

Thanks again for your help guys!

Edit: I'm getting the exception after all the layout methods are being called... Here is my custom layout... I'm still not sure how to fix it since all my try catch blocks don't trap the error...

- (void)prepareLayout
{
    NSLog(@"In prepareLayout.");

    [super prepareLayout];
    _cellCount = [[self collectionView] numberOfItemsInSection:0];
}

- (CGSize)collectionViewContentSize
{
    NSLog(@"In collectionViewContentSize.");
    @try {

       CGSize csize = [self collectionView].frame.size;
        NSLog(@"In here.");
        return csize;
    }
    @catch(NSException *exception)
    {
        NSLog(@"collectionViewContentSize %@: %@",[exception name],[exception reason]);
    }

}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    @try {

    NSLog(@"In layoutAttributesForItemAtIndexPath. item %d", indexPath.row);


    UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);

    NSLog(@"firstRun %d", self.firstRun);
    //NSLog(@"panStatus %@", self.panStatus);

    if (self.firstRun == 0) {
        CGFloat x = 0, y = 0;
        CGFloat boundx = self.collectionView.frame.size.width * 0.70;   // Use the whole canvas
        CGFloat boundy = self.collectionView.frame.size.height * 0.70; // Use the whole canvas
        while (x < 50 || x > boundx) x = arc4random() % (int)boundx + indexPath.item;
        while (y < 50 || y > boundy) y = arc4random() % (int)boundy + indexPath.item;

        NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
        if (self.positions)
            dictionary = [self.positions mutableCopy];
        [dictionary setObject:[NSValue valueWithCGPoint:CGPointMake(x, y)] forKey:@(indexPath.item)];
        if (!self.positions)
            self.positions = [[NSDictionary alloc] init];
        self.positions = dictionary;

        attributes.center = CGPointMake(x, y);
    } else if (self.firstRun > 0) {
        CGPoint point = [[self.positions objectForKey:@(indexPath.item)] CGPointValue];
        attributes.center = CGPointMake(point.x, point.y);
    }

    NSLog(@"END layoutAttributesForItemAtIndexPath. item %d", indexPath.row);
        return attributes;

    }
    @catch(NSException *exception)
    {
        NSLog(@"layoutAttributesForItemAtIndexPath %@: %@",[exception name],[exception reason]);
    }
}

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    @try {

    NSLog(@"layoutAttributesForElementsInRect ...");

    NSMutableArray* attributes = [NSMutableArray array];
    for (NSInteger i=0 ; i < self.cellCount; i++) {
        NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:1];
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
    }
    NSLog(@"END layoutAttributesForElementsInRect ...");
        return attributes;

    }
    @catch(NSException *exception)
    {
        NSLog(@"layoutAttributesForElementsInRect %@: %@",[exception name],[exception reason]);
    }
}
Josh Katz
  • 193
  • 1
  • 1
  • 10
  • Does the main viewController class you use implement the UICollectionViewDataSource protocol? Did you set your viewController (or whatever) as the dataSource for the UICollectionView? – Cashew Oct 14 '12 at 01:12
  • Yes. It implements 2 protocols: UICollectionViewDelegate and UICollectionViewDataSource. I've set it as the dataSource as well as delegate for the 2 UICollectionViews on it. – Josh Katz Oct 14 '12 at 02:55

6 Answers6

9

I had this happen when running reloadData from a gesture recognizer, setting delaysTouchesBegan on the gesture recognizer helped.

Keke
  • 91
  • 1
  • 2
6

The key here is this number from your error:

2147483647

This is the numeric value of NSNotFound. Somewhere in your code you are doing something like indexOfObject: against an array, and passing the result of that into one of your collection view or layout methods as a section or item index, which is causing the error. This isn't happening in the code posted in your question, but hopefully you'll be able to find it now.

jrturton
  • 113,418
  • 30
  • 247
  • 261
5

This worked for me:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView reloadData];
});
Hampden123
  • 1,230
  • 1
  • 14
  • 16
  • Any changes to GUI need to be triggered from a main thread. When changing or refreshing UICollectionView from a block, delay or gesture recognizer, you need to dispatch it on the main queue (as described by @Hampden123 above). – drpawelo Dec 14 '15 at 10:47
4

I just ran into this assert myself. It fired because I was reloading the collection data on a touch (swipe) event.

The initial touch (begin) triggered a didHighlightItemAtIndexPath: message, and next the collection data changed (via the swipe), and then it failed to trigger the didUnhighlightItemAtIndexPath: message on the touch (lift).

Since I don't need highlighting, the solution was to implement this method in my controller:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    return NO;
}

I hope that helps anyone else running into this.

Daltsy
  • 86
  • 7
  • 1
    I just wanted to say, the problem I had was reloading the collection data on a long press touch event. Removing reloadData fixed my problem. – J.C. Feb 01 '13 at 22:01
3

Never reload an UICOllectionView's data on a method executed by a gesture recognizer. Following this rule completely solved this problem. Daltsy's suggestion to deactivate highligting in the UICollectionView also prevents the error but also deactivates all "didSelectItemAtIndexPath" delegate messages. Be careful as you might end up with an almost useless UICollectionView - like I did a couple of minutes ago.

Andreas Zöllner
  • 234
  • 3
  • 11
0

Instead of disabling highlighting, I've found that making sure that no cells are highlighted before calling reloadData prevents the "request for number of items before section" error. It's the

UICollectionViewController.CollectionView.deselectItemAtIndexPath method or DeselectItem in Xamarin.

I save a handle to the currently selected item in the model data, clear the collection view's highlighting, calculate new NSIndexPaths for the items, and then call reloadData. The previously selected item is selected again by using the handle to find it via its possibly new NSIndexPath property.

selectItemAtIndexPath:animated:scrollPosition: or SelectItem in Xamarin.

Gary Z
  • 145
  • 2
  • 6