Search code examples
iosuicollectionviewuicollectionviewlayoutuicollectionreusableview

Issues inserting into UICollectionView section which contains a footer


I've got a typical UICollectionView which is using UICollectionViewFlowLayout in a vertical fashion. I'm using a rest API with pagination to populate the collection view. In order to trigger the next page to download, I'm using the delegate when it asks for the footer layout:

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
    RDLoadMoreReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"RDLoadMoreReusableView" forIndexPath:indexPath];
    view.delegate = self;
    [view start];
    [self loadNextPageOfAssets];
    return view;
}

Here is the code behind my loadNextPageOfAssets:

-(void)loadNextPageOfAssets{
    __weak RDRadiusGridViewController *weakSelf = self;

    // Increment page and request assets. They are added to cluster.assets before the completion block is called.
    self.cluster.pagination.page++;
    [self.cluster requestAssetsWithCompletionBlock:^(NSArray *assets) {

        SM_LOG_DEBUG(@"page.total: %ld assets.count: %ld", self.cluster.pagination.totalCount, (long)assets.count);

        NSMutableArray *indexPaths = [[NSMutableArray alloc]initWithCapacity:assets.count];
        for(NSUInteger index = 0; index < assets.count; index++){
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.cluster.assets.count - assets.count + index inSection:0];
            SM_LOG_DEBUG(@"Inserting indexPath: %ld:%ld", (long)indexPath.item, (long)indexPath.section);
            [indexPaths addObject:indexPath];
        }
        [weakSelf.collectionView performBatchUpdates:^{
            [weakSelf.collectionView insertItemsAtIndexPaths:indexPaths];
        } completion:^(BOOL finished) {

        }];

//            [weakSelf.collectionView reloadData];
    }];
}

When I run I can go to my ViewController and see the first page of assets loaded. If I scroll down I'll see the footer view (which contains a spinner), but then the code will break at the exception break point at line:

[weakSelf.collectionView insertItemsAtIndexPaths:indexPaths];

Assertion failure in -[UICollectionViewData layoutAttributesForSupplementaryElementOfKind:atIndexPath:], /SourceCache/UIKit/UIKit-3185.20/UICollectionViewData.m:829


Then if I continue it crashes with the error:

2014-06-11 16:39:58.335 Radius-iOS[4901:525006] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no UICollectionViewLayoutAttributes instance for -layoutAttributesForSupplementaryElementOfKind: UICollectionElementKindSectionFooter at path {length = 2, path = 0 - 0}' * First throw call stack:


This is puzzling to me. layoutAttributesForSupplementaryElementOfKind sounds like it's seomthing to do with the layout class, but I'm not using a custom flow layout rather the default one supplied. It sounds like Apple's code is mad but I feel that I'm using everything correctly.

Now if I move the call:

[self loadNextPageOfAssets];

from the supplementary cell dequeue to teh UICollectionViewCell deque, and remove the footer views all together, then the insert works great.

For now I'm calling reloadData instead of insert, but this is UGLY.

Am I overlooking something about the footers?


Solution

  • The answer provided by VaporwareWolf is a bit of a hack. By returning a tiny size instead of a zero size, the supplementary view will always exist but at a size too small to see. So that's why it fixes the NSInternalInconsistencyException

    But, there is a real solution.

    After adding the data to the datasource and before calling insertItemsAtIndexPaths, just invalidate the layout on the collectionview to make it aware of the changes.

    Objective-C

    [self.collectionView.collectionViewLayout invalidateLayout]
    

    Swift

    collectionView.collectionViewLayout.invalidateLayout()