UITableView 在同一批次更新中重新加载和移动行导致“尝试为单元格创建两个动画”

Posted

技术标签:

【中文标题】UITableView 在同一批次更新中重新加载和移动行导致“尝试为单元格创建两个动画”【英文标题】:UITableView reload and move rows in same batch update results in 'Attempt to create two animations for cell' 【发布时间】:2015-01-14 12:10:12 【问题描述】:

这是一个简单的例子(基本上是由“Master-Detail”Xcode 模板制作的)。

我正在尝试在 UITableView 的批量更新中移动一行(“B”)并重新加载另一行(“A1”->“A2”)。

- (void)awakeFromNib

    [super awakeFromNib];
    self.objects = @[@"B", @"A1"];


- (IBAction)boom

    self.objects = @[@"A2", @"B"];

    NSIndexPath *path0 = [NSIndexPath indexPathForRow:0 inSection:0];
    NSIndexPath *path1 = [NSIndexPath indexPathForRow:1 inSection:0];

    [self.tableView beginUpdates];
    [self.tableView reloadRowsAtIndexPaths:@[path1] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView moveRowAtIndexPath:path0 toIndexPath:path1];
    [self.tableView endUpdates];

这会导致以下异常:

2015-01-14 17:58:51.290 batchtest[34004:806671] *** Assertion failure in -[_UITableViewUpdateSupport _setupAnimationsForNewlyInsertedCells], /SourceCache/UIKit_Sim/UIKit-3318.16.14/UITableViewSupport.m:1216
2015-01-14 17:58:51.335 batchtest[34004:806671] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempt to create two animations for cell'
*** First throw call stack:
(
    0   CoreFoundation                      0x00946946 __exceptionPreprocess + 182
    1   libobjc.A.dylib                     0x005cfa97 objc_exception_throw + 44
    2   CoreFoundation                      0x009467da +[NSException raise:format:arguments:] + 138
    3   Foundation                          0x00243810 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 118
    4   UIKit                               0x010b18e5 -[_UITableViewUpdateSupport(Private) _setupAnimationsForNewlyInsertedCells] + 8447
    5   UIKit                               0x010bb422 -[_UITableViewUpdateSupport _setupAnimations] + 207
    6   UIKit                               0x00df8bc1 -[UITableView _updateWithItems:updateSupport:] + 2089
    7   UIKit                               0x00df24a8 -[UITableView _endCellAnimationsWithContext:] + 13867
    8   UIKit                               0x00e07b6f -[UITableView endUpdatesWithContext:] + 51
    9   UIKit                               0x00e07b9d -[UITableView endUpdates] + 41

当我尝试将@[path0] 传递给reloadRowsAtIndexPaths 而不是@[path1] 时,也会发生同样的事情。

对我来说,这看起来像是一个 ios 错误。有办法解决吗?我做错了吗?

【问题讨论】:

这是 Apple 工程部门对此问题的回答:***.com/a/9642438/2128900。似乎您必须使用一个丑陋的解决方法:“只需进入单元格并更改内容”。 谢谢,米哈乌。问题在于重新加载正在移动的同一行(顺便说一句,这也是有道理的)。我正在尝试重新加载 另一个 单元格。无论如何,我会考虑一些通用的方法来直接更新单元格而不是调用“重新加载”(显然我真正在做的并不是这个带有两个单元格的示例)。 请参考:***.com/questions/11372884/… 【参考方案1】:

在我看来,这个例外没有任何问题。 reloadRowsAtIndexPaths:withRowAnimation: 为正在重新加载的行创建动画,moveRowAtIndexPath:toIndexPath: 也创建动画。很明显,UITableView 无法同时处理 2 个动画并抛出异常。

我想将UITableViewRowAnimationNone 传递给reloadRowsAtIndexPaths:withRowAnimation: 可以解决问题,但不确定;不过,值得一试。

我会提出这样的解决方法:

[self.tableView moveRowAtIndexPath:path0 toIndexPath:path1];
dispatch_async(dispatch_get_main_queue(), ^
    self.objects = @[@"A2", @"B"];
    [self.tableView reloadRowsAtIndexPaths:@[path1]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
);

或者这个:

[CATransaction begin];
[CATransaction setCompletionBlock:^
    self.objects = @[@"A2", @"B"];
    [self.tableView reloadRowsAtIndexPaths:@[path1]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
];
[self.tableView moveRowAtIndexPath:path0 toIndexPath:path1];
[CATransaction commit];

或者这个:

[UIView animateWithDuration:0.2 animations:^
    [self.tableView moveRowAtIndexPath:path0 toIndexPath:path1];
 completion:^(BOOL finished)
    self.objects = @[@"A2", @"B"];
    [self.tableView reloadRowsAtIndexPaths:@[path1]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
];

【讨论】:

UITableView 可以在批量更新中处理多个动画——这就是批量更新的用途。它抱怨的是单个 cell 有两个动画。但在我看来,重新加载动画会转到 [最初] 第二个单元格,而移动动画会转到 [最初] 第一个单元格。 我猜所有的动画都是同时创建的,所以你会同时为path1制作几个动画 UITableViewRowAnimationNone 没有帮助。此外,我不想引入任何异步,因为如果在两者之间发生另一个更新,它将中断。但是,首先“移动”,然后在单独的批次中同步“重新加载”,确实有帮助。但我看不出这背后有任何原因,特别是因为“重新加载,然后移动”无法正常工作。 path1 in "move" 是 "B" 单元格将移动到的位置,它与 最初cell 不同在path1 我不认为 CATransaction 与 dispatch_async 有什么不同,比如它不会等待上一个动画结束或任何事情,因为它不是在那里创建的,而是在 endUpdates 中创建的。跨度> 【参考方案2】:

我解决了这个问题,只是取消了[tableView beginUpdates][tableView endUpdates]。 顺便说一句,iOS 8.4 和 9.x 解决了这个问题,但是,iOS 8.1 不行。

【讨论】:

【参考方案3】:

就像@Dawn Song 说的:在iOS 8.4 up,这个问题是不会发生的,但我猜只有8.1 down(还没有测试8.0及down)

在 iOS8.1 中,也许你应该一个一个地更新单元格。这对我有用: 你的代码:

[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:@[path1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView moveRowAtIndexPath:path0 toIndexPath:path1];
[self.tableView endUpdates];

尝试更改为:

[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:@[path1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
[self.tableView beginUpdates];
[self.tableView moveRowAtIndexPath:path0 toIndexPath:path1];
[self.tableView endUpdates];

但请注意:您的 path1 应该只有 1 个 NSIndexPath

【讨论】:

以上是关于UITableView 在同一批次更新中重新加载和移动行导致“尝试为单元格创建两个动画”的主要内容,如果未能解决你的问题,请参考以下文章

NSMutableArray 未更新且 UITableView 未重新加载

在 iOS 中重新加载特定的 UITableView 单元格

重新加载行以更新 UITableView 中的单元格高度时出现问题

UITableview 没有重新加载

CoreData:在返回 UITableView 之前重新加载数据

重新加载 UITableView 而不重新加载标题部分