4

Short question:

Is there a way to add and remove supplementary views like cells and section in a performBatchUpdates: block similar to the insertItemsAtIndexPaths: deleteItemsAtIndexPaths: or even reloadItemsAtIndexPaths: methods?

Long explanation:

I use a UICollectionView with sections. The cells are laid out like a grid and each line can have up to 6 cells. I add additional supplementary views if the total amount of cells do not fill up all lines of a section. The additional supplementary views just fill up the empty space. So the result should be that each section is completely filled with all available cells and the (possible) remaining empty spots are filled with supplementary views. So each line has 6 visual elements, eather a cell or a supplementary view.

To achieve that I implemented a custom UICollectionViewFlowLayout based layout with the following prepareLayout: implementation:

- (void)prepareLayout {
    [super prepareLayout];

    self.supplementaryViewAttributeList = [NSMutableArray array];
    if(self.collectionView != nil) {
        // calculate layout values
        CGFloat contentWidth = self.collectionViewContentSize.width - self.sectionInset.left - self.sectionInset.right;
        CGFloat cellSizeWithSpacing = self.itemSize.width + self.minimumInteritemSpacing;
        NSInteger numberOfItemsPerLine = floor(contentWidth / cellSizeWithSpacing);
        CGFloat realInterItemSpacing = (contentWidth - (numberOfItemsPerLine * self.itemSize.width)) / (numberOfItemsPerLine - 1);

        // add supplementary attributes
        for (NSInteger numberOfSection = 0; numberOfSection < self.collectionView.numberOfSections; numberOfSection++) {
            NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:numberOfSection];
            NSInteger numberOfSupplementaryViews = numberOfItemsPerLine - (numberOfItems % numberOfItemsPerLine);

            if (numberOfSupplementaryViews > 0 && numberOfSupplementaryViews < 6) {
                NSIndexPath *indexPathOfLastCellOfSection = [NSIndexPath indexPathForItem:(numberOfItems - 1) inSection:numberOfSection];
                UICollectionViewLayoutAttributes *layoutAttributesOfLastCellOfSection = [self layoutAttributesForItemAtIndexPath:indexPathOfLastCellOfSection];

                for (NSInteger numberOfSupplementor = 0; numberOfSupplementor < numberOfSupplementaryViews; numberOfSupplementor++) {
                    NSIndexPath *indexPathOfSupplementor = [NSIndexPath indexPathForItem:(numberOfItems + numberOfSupplementor) inSection:numberOfSection];
                    UICollectionViewLayoutAttributes *supplementaryLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:ARNCollectionElementKindFocusGuide withIndexPath:indexPathOfSupplementor];
                    supplementaryLayoutAttributes.frame = CGRectMake(layoutAttributesOfLastCellOfSection.frame.origin.x + ((numberOfSupplementor + 1) * (self.itemSize.width + realInterItemSpacing)), layoutAttributesOfLastCellOfSection.frame.origin.y, self.itemSize.width, self.itemSize.height);
                    supplementaryLayoutAttributes.zIndex = -1;

                    [self.supplementaryViewAttributeList addObject:supplementaryLayoutAttributes];
                }
            }
        }
    }
}

This works great if all data is available from the beginning and if there is no change to the data. But it fails miserably if data gets added and removed during the lifetime. Inserts and deletes of cells in a performBatchUpdates: mess up the supplementary views.

What should happen:

For every cell that is inserted with insertItemsAtIndexPaths: the supplementary view at the same indexPath should be removed. Furthermore for every cell that is deleted with deleteItemsAtIndexPaths: a new supplementary view should be added at the same indexPath.

The Problem:

The prepareLayout: of my layout is called and calculates the correct layoutAttributes. But layoutAttributesForSupplementaryViewOfKind:atIndexPath: is called for weird indexPaths. Specifically the layoutAttributesForSupplementaryViewOfKind:atIndexPath: is called for old indexPaths which got removed from the supplementaryViewAttributeList array!

Example:

In the case where a new cell gets inserted, the prepareLayout: correctly removes the layoutAttributes, but the layoutAttributesForSupplementaryViewOfKind:atIndexPath: still gets called for the not longer available and deleted indexPath! And only for that. There is no additional call for any other indexPaths for all the other supplementary views.

Why is layoutAttributesForSupplementaryViewOfKind:atIndexPath: called for indexPaths that are not available any more? How would I delete, insert or reload the supplementary views correctly? What do I miss?

Source:

The full source can be found here:

Custom Layout: https://github.com/sarn/ARNClassicFilms/blob/master/classicFilms/classicFilms/layout/ARNCollectionViewFocusGuideFlowLayout.m

CollectionView Controller: https://github.com/sarn/ARNClassicFilms/blob/master/classicFilms/classicFilms/view-controllers/ARNMovieOverviewController.m (performBatchUpdates: is in the last method of the class)

Stefan Arn
  • 1,156
  • 12
  • 29
  • Hey i just happened upon your post and was wondering if you found anything? Im experiencing a [very similar issue](http://stackoverflow.com/questions/38005641/no-uicollectionviewlayoutattributes-instance-for-layoutattributesforsupplementa/38005845#38005845) – Genhain Jun 24 '16 at 09:10
  • 1
    I actually asked an Apple Engineer at the WWDC last week. The solution he offered was to add an `[[collectionView collectionViewLayout] invalidateLayout];` to the `performBatchUpdates:` block. This actually works for my case. Maybe this can help in your case as well – Stefan Arn Jun 24 '16 at 15:53
  • Means i was close then, i added it just before the block, will give it a try and update my question if it is fruitful. – Genhain Jun 24 '16 at 21:33
  • 1
    Yeah i tried adding it and it still didnt update the supplementary views properly...[one of the issues being this](http://stackoverflow.com/questions/38005641/no-uicollectionviewlayoutattributes-instance-for-layoutattributesforsupplementa) – Genhain Jul 10 '16 at 07:29

2 Answers2

3

Your comment was not what did the trick for me, i found another bit of code and added it and it worked, and even removed the above suggestion, and it still worked. Collection views are certainly powerful but forget one function such as this one...

- (NSArray<NSIndexPath *>   *)indexPathsToDeleteForSupplementaryViewOfKind:(NSString *)elementKind
{
    return self.removedIndexPaths;
}

and things can go fairly awry.

Genhain
  • 1,937
  • 1
  • 19
  • 38
1

One solution is to add a [[collectionView collectionViewLayout] invalidateLayout] to the performBatchUpdates: block. This forces the layout to recalculate and drop the not valid indexPaths.

Stefan Arn
  • 1,156
  • 12
  • 29