1
votes

I'm using collection view with custom layout, Calling webservice I received first time from the API returns 20 objects, and the second time it will return 1 object, while reloading the data applications throws the following error.

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: UICollectionView received layout attributes for a cell with an index path that does not exist: {length = 2, path = 0 - 0}

Code piece of creating new layout

-(void)doNewLayout 
{
 id<UICollectionViewDelegateJSPintLayout> delegate = (id<UICollectionViewDelegateJSPintLayout>)self.collectionView.delegate;

// get column width from delegate.  If the method isn't implemented fall back to our property
    NSUInteger columnWidth = self.columnWidth;
    if(delegate && [delegate respondsToSelector:@selector(columnWidthForCollectionView:layout:)])
    {
        columnWidth = [delegate columnWidthForCollectionView:self.collectionView
                                                      layout:self];
    }

    // find out how many cells there are
    NSUInteger cellCount = [self.collectionView numberOfItemsInSection:0];

    // get max number of columns from the delegate.  If the method isn't implemented, fall back to our property
    NSUInteger maximumNumberOfColumns = self.numberOfColumns;
    if(delegate && [delegate respondsToSelector:@selector(maximumNumberOfColumnsForCollectionView:layout:)]){
        maximumNumberOfColumns = [delegate maximumNumberOfColumnsForCollectionView:self.collectionView layout:self];
    }

    // build an array of all the cell heights.
    NSMutableArray* cellHeights = [NSMutableArray arrayWithCapacity:cellCount];
    for(NSUInteger cellIndex = 0; cellIndex < cellCount; ++cellIndex)
    {
        CGFloat itemHeight = self.itemHeight;  // set default item size, then optionally override it
        if(delegate && [delegate respondsToSelector:@selector(collectionView:layout:heightForItemAtIndexPath:)])
        {
            itemHeight = [delegate collectionView:self.collectionView
                                           layout:self
                         heightForItemAtIndexPath:[NSIndexPath indexPathForItem:cellIndex
                                                                      inSection:0]];
        }

        cellHeights[cellIndex] = @(itemHeight);
    }

    // now build the array of layout attributes
    self.pendingLayoutAttributes = [NSMutableArray arrayWithCapacity:cellCount];

    // will need an array of column heights
    CGFloat* columnHeights = calloc(maximumNumberOfColumns,sizeof(CGFloat));  // calloc() initializes to zero.
    CGFloat contentHeight = 0.0;
    CGFloat contentWidth = 0.0;
    for(NSUInteger cellIndex = 0; cellIndex < cellCount; ++cellIndex)
    {
        CGFloat itemHeight = [cellHeights[cellIndex] floatValue];

        // find shortest column
        NSUInteger useColumn = 0;
        CGFloat shortestHeight = DBL_MAX;
        for(NSUInteger col = 0; col < maximumNumberOfColumns; ++col)
        {
            if(columnHeights[col] < shortestHeight)
            {
                useColumn = col;
                shortestHeight = columnHeights[col];
            }
        }

        NSIndexPath* indexPath = [NSIndexPath indexPathForItem:cellIndex
                                                     inSection:0];
        UICollectionViewLayoutAttributes* layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

        layoutAttributes.size = CGSizeMake(columnWidth,itemHeight);
        layoutAttributes.center = CGPointMake((useColumn * (columnWidth + self.interitemSpacing)) + (columnWidth / 2.0),columnHeights[useColumn] + (itemHeight / 2.0));
        self.pendingLayoutAttributes[cellIndex] = layoutAttributes;
        columnHeights[useColumn] += itemHeight;
        if(columnHeights[useColumn] > contentHeight)
            contentHeight = columnHeights[useColumn];
        CGFloat rightEdge = (useColumn * (columnWidth + self.interitemSpacing)) + columnWidth;
        if(rightEdge > contentWidth)
            contentWidth = rightEdge;
        columnHeights[useColumn] += self.lineSpacing;
    }
    self.contentSize = CGSizeMake(contentWidth,contentHeight+100);

    free(columnHeights);
}

Any Quick solution would be really appreciated.Thanks

1
You might need to show some more code, how are you telling the collection view how many cells it should be drawing? - Wez
Add your custom layout code, it seems you are not creating layoutAttributes for some all inexPaths, it happens when you are inserting items to collection view but not changing the custom layouts - Adnan Aftab
@C_X Added code snippet of new layout creation. - Sathish
So this will calculate one time or for every new item... can you add code where you are calling collectionView.reloadData() as well. - Adnan Aftab
Which line is causing the error? Add an exception breakpoint. It will also show the call stack at the time. - Fogmeister

1 Answers

1
votes

There are couple of things need consideration , as you are custom layout then you need to create layoutAttribute for each indexPath. In your case your data source array count and offerModel.arrayOffers and self.pendingLayoutAttributes should be same, if not then it might be problem and its crushable problem if offerModel.arrayOffershave more items then self.pendingLayoutAttributes.

If you are loading data async then make sure when you are adding rows in arraysOffers also add layoutAttributes in customLayout pendLayoutAttributes,which I think you are not doing at the moment, do that by adding a method and provide new indexPaths to that which create layoutAttributes.

I usually do like this

- (void)insertItems:(NSArray*)items
{
    NSInteger startIndex = <start index for new item>;
    [self.items addObjectsFromArray:items];
    [self calculateLayoutForItems:items startIndex:startIndex];
}

This method will calculate layoutAttributes

- (void)calculateLayoutForItems:(NSArray*)items startIndex:(NSInteger)startIndex
{
    // collection view and loop over them as well and nest indexPath creation

    NSInteger section = 0;
    NSInteger endIndex = self.items.count;

    // Lets create indexPaths for provided items and get there frames
    for (NSInteger index = startIndex ;index < endIndex; index++)
    {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section];
        CGRect frame = [self frameForItemAtIndexPath:indexPath];// call your method for frame at indexPath

        UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        itemAttributes.frame = frame;
        self.pendingLayoutAttributes[indexPath] = itemAttributes; // you can use your index
    }
}

Now when you got more items in data source call this method on customLayoutObject [self.customLayout insertItems:newItemsArray];

Also if you have stored properties then its worth overriding invalidate method in custom layout to reset all properties to initial state,Then you can just invalidate customLayout before reloadData method, then collection view will force it to compute layout again.