I've identified a simple edge case in UICollectionView's batchUpdate operations which should work but fails with
attempt to perform an insert and a move to the same index path ( {length = 2, path = 0 - 2})
My operation is to go from [A, B] --> [C, B', A]. This is done with updates:
Clearly the error is wrong, the insert index is different from the move TO index.
I set up demo to make sure this is a UICollectionView problem, here is my code if you care to see it in action:
@implementation ViewController {
UICollectionView *_collection;
NSArray *_values;
}
- (instancetype)init
{
self = [super init];
if (self) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
_collection = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collection.dataSource = self;
_values = @[@1, @2];
[_collection registerClass:[UICollectionViewCell class]
forCellWithReuseIdentifier:@"reuse"];
[self.view addSubview:_collection];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
_collection.frame = self.view.bounds;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self triggerBatchUpdate];
});
}
- (void)triggerBatchUpdate {
[_collection performBatchUpdates:^{
_values = @[@4, @5, @1];
[_collection insertItemsAtIndexPaths:@[[self index:0]]];
[_collection moveItemAtIndexPath:[self index:0] toIndexPath:[self index:2]];
// Works with this line
// [_collection moveItemAtIndexPath:[self index:1] toIndexPath:[self index:1]];
// Fails with this line
[_collection reloadItemsAtIndexPaths:@[[self index:1]]];
} completion:nil];
}
- (NSIndexPath *)index:(NSUInteger)ind {
return [NSIndexPath indexPathForRow:ind inSection:0];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return _values.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [_collection dequeueReusableCellWithReuseIdentifier:@"reuse"
forIndexPath:indexPath];
cell.backgroundColor = [UIColor grayColor];
return cell;
}
@end
The fact that the code works if I use
[_collection moveItemAtIndexPath:[self index:1] toIndexPath:[self index:1]];
instead of
[_collection reloadItemsAtIndexPaths:@[[self index:1]]];
makes me suspicious. Those two operations should be equivalent.
Any idea what's going on here? Is this, as I believe, a UICollectionView bug?
EDIT: Another crash that turns out to be related to this:
attempt to perform a delete and a move from the same index path ( {length = 2, path = 0 - 5})
It does seem to be a bug, although while the documentation for performBatchUpdates:completion
explains the context for indices in insert and delete operations, and mentions that reload operations are permitted, it doesn't detail the context for indices in reload operations.
After some experimentation, it seems that "under the covers" a reload is implemented as a delete and an insert. This seems to cause an issue where there is an overlap between some other operation and the reload index.
I did find, however, that replacing the reload with an explicit delete and insert seems to work, so you can use:
- (void)triggerBatchUpdate {
[_collection performBatchUpdates:^{
_values = @[[UIColor blueColor], [UIColor yellowColor], [UIColor redColor]];
[_collection moveItemAtIndexPath:[self index:0] toIndexPath:[self index:2]];
[_collection insertItemsAtIndexPaths:@[[self index:0]]];
[_collection deleteItemsAtIndexPaths:@[[self index:1]]];
[_collection insertItemsAtIndexPaths:@[[self index:1]]];
} completion:nil];
}
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