When inserting or deleting an item from a UICollectionView, it seems like an extra cell appears during the animation, and this extra cell moves in the wrong direction. I've tried exactly the same with a UITableView, and there is no problem.
A video of the problem is here: https://dl.dropbox.com/u/11523469/CollectionViewBug.mov, with the collection view on the left and the table view on the right. The number in each cell is the indexPath.item value of the cell when the cell is created.
The problem is first noticeable in the video between 0:08 and 0:12 (insert) and then again at 0:16 to 0:20 (delete).
The project is available here: https://dl.dropbox.com/u/11523469/CollectionViewBug.zip
i.e. when inserting a cell, all cells below where the cell is being inserted move down to make room for the new cell. But this extra cell appears and overlaps the others and moves up.
Likewise, when deleting a cell, all cells below the cell being deleted move up to fill the gap where the cell used to be. But this extra cell appears and overlaps the others and moves down.
The first action to be performed on the collection view, either insert or delete, does not cause this problem. But on all subsequent actions the problem is there.
Has anyone else experienced the same problem with UICollectionView? Does anyone have a solution or workaround?
Thanks!
I came up with a workaround which seems to fix the problem but is very specific to the example provided. My guess is that when cells are reused they have the wrong starting point which causes the weird animations.
I changed the Storyboard to use a subclass of UICollectionViewFlowLayout:
// MyFlowLayout - subclass of UICollectionViewFlowLayout
#import "MyFlowLayout.h"
@interface MyFlowLayout ()
@property (strong) NSMutableArray *deleteIndexPaths;
@property (strong) NSMutableArray *insertIndexPaths;
@property (assign) float rowOffset;
@end
@implementation MyFlowLayout
-(id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
// minimumInteritemSpacing may be adjusted upwards but this example ignores that
self.rowOffset = self.itemSize.height + self.minimumInteritemSpacing;
}
return self;
}
// As per Mark Pospesel corrections to CircleLayout
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
// Keep track of insert and delete index paths
[super prepareForCollectionViewUpdates:updateItems];
self.deleteIndexPaths = [NSMutableArray array];
self.insertIndexPaths = [NSMutableArray array];
for (UICollectionViewUpdateItem *update in updateItems)
{
if (update.updateAction == UICollectionUpdateActionDelete)
{
[self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
}
else if (update.updateAction == UICollectionUpdateActionInsert)
{
[self.insertIndexPaths addObject:update.indexPathAfterUpdate];
}
}
}
- (void)finalizeCollectionViewUpdates
{
[super finalizeCollectionViewUpdates];
// release the insert and delete index paths
self.deleteIndexPaths = nil;
self.insertIndexPaths = nil;
}
// The next two methods have misleading names as they get called for all visible cells on both insert and delete
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
// Must call super
UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
if ([self.insertIndexPaths containsObject:itemIndexPath]) {
// Initial position for an inserted cell is it's final position - fades in
CGRect frame = attributes.frame;
frame.origin.y = itemIndexPath.row * self.rowOffset;
attributes.frame = frame;
attributes.zIndex = -1; // stop the inserted cell bleeding through too early in the animation
}
if ([self.deleteIndexPaths count]) {
NSIndexPath *deletedPath = self.deleteIndexPaths[0]; // Might be more than one but this example ignores that
if (itemIndexPath.row > deletedPath.row) {
// Anything after the deleted cell needs to slide up from the position below it's final position
// Anything before the deleted cell doesn't need adjusting
CGRect frame = attributes.frame;
frame.origin.y = ((itemIndexPath.row + 1) * self.rowOffset);
attributes.frame = frame;
}
}
return attributes;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// I would have expected the final positions to already be correct but my guess is that re-used cells
// are not considered until after the animation block settings have been generated
CGRect frame = attributes.frame;
frame.origin.y = itemIndexPath.row * self.rowOffset;
attributes.frame = frame;
if ([self.deleteIndexPaths containsObject:itemIndexPath]) {
// Fade out the deleted cell
attributes.alpha = 0.0;
}
return attributes;
}
@end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With