UICollectionView BatchUpdate 边缘案例失败

Posted

技术标签:

【中文标题】UICollectionView BatchUpdate 边缘案例失败【英文标题】:UICollectionView BatchUpdate edge case fails 【发布时间】:2017-02-09 22:10:14 【问题描述】:

我在 UICollectionView 的 batchUpdate 操作中发现了一个简单的边缘情况,它应该可以工作但失败了

尝试执行插入和移动到相同的索引路径(length = 2, path = 0 - 2)

我的操作是从[A, B] --> [C, B', A]开始。这是通过更新完成的:

从索引 0 到 2 的移动 重新加载索引 1 索引 0 处的插入

显然错误是错误的,插入索引与移动TO索引不同。

我设置了演示以确保这是一个 UICollectionView 问题,如果您希望看到它的实际效果,这是我的代码:

@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

如果我使用代码可以工作的事实

[_collection moveItemAtIndexPath:[self index:1] toIndexPath:[self index:1]];

而不是

[_collection reloadItemsAtIndexPaths:@[[self index:1]]];

让我怀疑。这两个操作应该是等价的。

知道这里发生了什么吗?这是我认为的 UICollectionView 错误吗?

编辑: 事实证明与此有关的另一个崩溃:

尝试从同一索引路径(length = 2, path = 0 - 5)执行删除和移动

【问题讨论】:

如果在移动之前执行插入会怎样?对于仅包含 2 个项目的集合,从 0 到 2 的移动是无效的 不,这不会改变任何东西,也不应该。您在 batchUpdate 中执行操作的顺序无关紧要,它们按照苹果定义的顺序执行(首先删除,然后插入,然后移动)。 move FROM索引对应其原始状态的索引,TO索引是相对于最终状态的。 它看起来像一个错误。仅供参考,使用 [_collection moveItemAtIndexPath:[self index:1] toIndexPath:[self index:1]]; 实际上不起作用,因为第二个单元格没有重新加载。我使用颜色而不是数字,所以你可以看到发生了什么:gist.github.com/paulw11/025e6f3dbdfaf911a6dab95c6631cbe2 只有当我将重新加载移到完成块时它才有效 如果我摆脱了移动,离开了插入,然后尝试在索引 2 处重新加载项目,我得到一个错误,我试图在只有 2 个项目时删除项目 2。似乎重新加载已转换为删除和插入,这会导致索引在批处理期间发生更改的问题。如果我用索引 1 的离散删除替换重新加载,然后索引 1 的插入它可以工作 好收获!我对自己的假设完全错误(不确定它来自哪里,我想知道它是否曾经是真的?)我认为你是对的,从重新加载到删除和插入的转换完全有意义。有一个非常相似的崩溃,我无法通过删除和移动来重现,这加强了你的理论。用删除和插入替换重新加载似乎工作正常:) 你可以写一个答案来解释这一切,我会接受它。如果你不愿意,那很好,我会为后代做的。 【参考方案1】:

这似乎是一个错误,尽管performBatchUpdates:completion 的文档解释了插入和删除操作中索引的上下文,并提到允许重新加载操作,但它没有详细说明重新加载操作中索引的上下文.

经过一些实验,似乎“在幕后”重新加载实现为删除和插入。这似乎会导致一些其他操作与重新加载索引之间存在重叠的问题。

但是,我确实发现用显式删除和插入替换重新加载似乎可行,因此您可以使用:

- (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];

【讨论】:

以上是关于UICollectionView BatchUpdate 边缘案例失败的主要内容,如果未能解决你的问题,请参考以下文章

如何滚动另一个 UICollectionView 下方的 UICollectionView?

UICollectionView 单元内部已加载,但 UICollectionView 未显示

UICollectionView 断言失败 -[UICollectionView _updateWithItems:tentativelyForReordering:]

UITableviewCell 中的 UICollectionView。添加约束后,UICollectionView 不显示

将标题添加到 UICollectionView,而不是 UICollectionView 的部分

嵌套垂直 UICollectionView 和动态高度