问题
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 indexPath
s 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)
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/36386854/how-can-uicollectionview-supplementary-views-be-inserted-or-deleted-correctly