I'm writing a custom UICollectionViewFlowLayout
and I've noticed that initialLayoutAttributesForAppearingItemAtIndexPath:
and initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
will be called for all sections when I invoke performBatchUpdates:completion:
on the collection view. The result is that all sections have the animation applied to them instead of just the newly added section.
[collectionView performBatchUpdates:^{
currentModelArrayIndex++;
[collectionView insertSections:[NSIndexSet indexSetWithIndex:currentModelArrayIndex]];
[collectionView reloadSections:[NSIndexSet indexSetWithIndex:currentModelArrayIndex-1]];
} completion:^(BOOL finished) {
[collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:currentModelArrayIndex] atScrollPosition:UICollectionViewScrollPositionTop animated:YES];
}];
What I've tried so far is removing the call to performBatchUpdates:completion:
in lieu of simple updates, but the already existing sections (all of them) are animated in anyway. I've come up with a solution of checking to make sure that I'm changing the layout attributes of only the last section, but it feels hacky.
if (decorationIndexPath.section == [(id<UICollectionViewDataSource>)self.collectionView.delegate numberOfSectionsInCollectionView:self.collectionView] - 1)
{
layoutAttributes.alpha = 0.0f;
layoutAttributes.transform3D = CATransform3DMakeTranslation(-CGRectGetWidth(layoutAttributes.frame), 0, 0);
}
Is this the proper way to go about animating only some sections?
OK, I've got an answer; it's not a lot prettier than the previous one, but it keeps the layout from touching the datasource, so it's cleaner.
Basically, we need to override prepareForCollectionViewUpdates:
and finalizeCollectionViewUpdates
to keep track of the sections that we're inserting. I have a mutable set that contains NSNumber
instances of the sections that we're inserting.
-(void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
[super prepareForCollectionViewUpdates:updateItems];
[updateItems enumerateObjectsUsingBlock:^(UICollectionViewUpdateItem *updateItem, NSUInteger idx, BOOL *stop) {
if (updateItem.updateAction == UICollectionUpdateActionInsert)
{
[insertedSectionSet addObject:@(updateItem.indexPathAfterUpdate.section)];
}
}];
}
-(void)finalizeCollectionViewUpdates
{
[super finalizeCollectionViewUpdates];
[insertedSectionSet removeAllObjects];
}
Next, I check to see if the index path's section is included in that set when setting the initial layout attributes for items and decoration views.
-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath
{
UICollectionViewLayoutAttributes *layoutAttributes;
if ([elementKind isEqualToString:AFCollectionViewFlowLayoutBackgroundDecoration])
{
if ([insertedSectionSet containsObject:@(decorationIndexPath.section)])
{
layoutAttributes = [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath];
layoutAttributes.alpha = 0.0f;
layoutAttributes.transform3D = CATransform3DMakeTranslation(-CGRectGetWidth(layoutAttributes.frame), 0, 0);
}
}
return layoutAttributes;
}
-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
UICollectionViewLayoutAttributes *layoutAttributes;
if ([insertedSectionSet containsObject:@(itemIndexPath.section)])
{
layoutAttributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
layoutAttributes.transform3D = CATransform3DMakeTranslation([self collectionViewContentSize].width, 0, 0);
}
return layoutAttributes;
}
I'm returning nil
from these methods otherwise because nil
is the default value.
This has the added benefit of having nicer rotation animations, as well.