Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView BatchUpdate edge case fails

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:

  • A move from index 0 to 2
  • A reload of index 1
  • An insert at index 0

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})

like image 781
Dave Avatar asked Feb 06 '23 03:02

Dave


1 Answers

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];
}
like image 151
Paulw11 Avatar answered Feb 13 '23 07:02

Paulw11