撕掉我的头发试图让它发挥作用。我想执行[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
,
我删除的更详细的代码:
int index = (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
这样可行但是如果我收到推送通知(即收到新消息),删除应用程序时会崩溃并显示如下错误:
断言失败 - [UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-3347.44/UITableView.m:1327 2015-07-04 19:12:48.623 myapp [319:24083] ***由于未捕获的异常'NSInternalInconsistencyException'终止应用程序,原因: '尝试从第0部分删除仅包含1行的第1行 在更新'
之前
我怀疑这是因为我的数据源正在改变,数组的大小
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
删除时的引用将不一致,因为当推送通知触发刷新时,它会增加1。有什么方法可以解决这个问题吗?我是否更正deleteRowsAtIndexPaths
使用numberOfRowsInSection
方法?
答案 0 :(得分:5)
因此,为了解决您的问题,您需要确保在某些表格视图动画到位时您的数据源不会发生变化。我建议做以下事情。
首先,创建两个数组:messagesToDelete
和messagesToInsert
。这些将保存有关您要删除/插入哪些邮件的信息。
其次,将布尔属性updatingTable
添加到表视图数据源。
第三,添加以下功能:
-(void)updateTableIfPossible {
if (!updatingTable) {
updatingTable = [self updateTableViewWithNewUpdates];
}
}
-(BOOL)updateTableViewWithNewUpdates {
if ((messagesToDelete.count == 0)&&(messagesToInsert.count==0)) {
return false;
}
NSMutableArray *indexPathsForMessagesThatNeedDelete = [[NSMutableArray alloc] init];
NSMutableArray *indexPathsForMessagesThatNeedInsert = [[NSMutableArray alloc] init];
// for deletion you need to use original messages to ensure
// that you get correct index paths if there are multiple rows to delete
NSMutableArray *oldMessages = [self.messages copy];
for (id message in messagesToDelete) {
int index = (int)[self.oldMessages indexOfObject:message];
[self.messages removeObject:message];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[indexPathsForMessagesThatNeedDelete addObject:indexPath];
}
for (id message in messagesToInsert) {
[self.messages insertObject:message atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[indexPathsForMessagesThatNeedInsert addObject:indexPath];
}
[messagesToDelete removeAllObjects];
[messagesToInsert removeAllObjects];
// at this point your messages array contains
// all messages which should be displayed at CURRENT time
// now do the following
[CATransaction begin];
[CATransaction setCompletionBlock:^{
updatingTable = NO;
[self updateTableIfPossible];
}];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:indexPathsForMessagesThatNeedDelete withRowAnimation:UITableViewRowAnimationLeft];
[tableView insertRowsAtIndexPaths:indexPathsForMessagesThatNeedInsert withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
[CATransaction commit];
return true;
}
最后,您需要在所有想要添加/删除行的函数中包含以下代码。
添加消息
[self.messagesToInsert addObject:message];
[self updateTableIfPossible];
删除邮件
[self.messagesToDelete addObject:message];
[self updateTableIfPossible];
此代码的作用是确保数据源的稳定性。每当有更改时,您都需要将需要插入/删除的消息添加到数组中(messagesToDelete
和messagesToDelete
)。然后调用函数updateTableIfPossible
,它将更新表视图的数据源(并将动画更改),前提是当前没有动画正在进行中。如果正在进行动画,则在此阶段将无效。
然而,因为我们已添加完成
[CATransaction setCompletionBlock:^{
updatingTable = NO;
[self updateTableIfPossible];
}];
在动画结束时,我们的数据源将检查是否有任何需要应用于表视图的新更改,如果是,则会更新动画。
这是更安全的数据源更新方式。如果它适合你,请告诉我。
答案 1 :(得分:2)
删除一行
**arrptr
删除部分
注意:如果您的TableView有多个部分,那么当部分只包含一行而不是删除行时,您必须删除整个部分
int
答案 2 :(得分:1)
我对上面的代码进行了一些测试。并且你不能同时做到这一点,至少我们能做的是:
//This will wait for `deleteRowsAtIndexPaths:indexes` before the `setCompletionBlock `
//
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[tableView reloadData];
}];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
[CATransaction commit];
在您的代码中有:
/*
int index = (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
*/
这是不安全的,因为如果通知被触发并且由于某种原因[self.tableView reloadData];
在deleteRowsAtIndexPaths
之前调用将导致崩溃,因为tableView当前正在更新数据然后由{中断{1}},请尝试以下序列进行检查:
deleteRowsAtIndexPaths:
嗯..回到你的代码,让我们有一个可能导致崩溃的模拟..这只是一个假设,所以这不是100%肯定(99%)。 :)
假设/*
...
[self.tableView reloadData];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
This will cause a crash...
*/
等于nil;
self.messageToDelete
我的建议是先检查int index = (int)[self.messages indexOfObject: nil];
// since you casted this to (int) with would be 0, so `index = 0`
[self.messages removeObject: nil];
// self.messages will remove nil object which is non existing therefore
// self.messages is not updated/altered/changed
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
// indexPath.row == 0
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
// then you attempted to delete index indexPath at row 0 in section 0
// but the datasource is not updated meaning this will crash..
//
// Same thing will happen if `self.messageToDelete` is not existing in your `self.messages `
:
self.messageToDelete
希望这很有帮助,干杯! ;)
答案 3 :(得分:1)
'尝试从更新'
之前仅包含1行的第0部分删除第1行这表示您的索引路径在删除期间引用了第1行。但是第1行不存在。仅存在第0行(如该部分,它们从零开始)。
那么你如何得到一个大于元素数的indexPath?
我们可以看到插入行的通知处理程序吗?你可以做一些黑客攻击,如果动画正在进行中,你会在通知处理期间执行Selector:withDelay:以留出时间让动画完成。
答案 4 :(得分:1)
在您的代码中,崩溃的原因是:您正在更新数组但不更新UITableView
的数据源。因此,您的dataSource还需要在调用endUpdates时反映更改。
根据Apple文档。
要为行和节的批量插入,删除和重新加载设置动画,请在连续调用beginUpdates和endUpdates定义的动画块中调用相应的方法。如果不在此块中调用插入,删除和重新加载方法,则行和节索引可能无效。可以嵌套对beginUpdates和endUpdates的调用;所有索引都被视为只有外部更新块。
在一个块结束时 - 也就是说,在endUpdates返回之后 - 表视图查询其数据源并像往常一样委托行和段数据。因此,应更新支持表视图的集合对象以反映新的或删除的行或节。
示例强>:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
希望这对你有所帮助。
答案 5 :(得分:0)
我认为这里的许多评论都提到了答案,但我会尝试梳理一下。
第一个问题是,您似乎没有在beginUpdates
/ endUpdates
方法中包含删除方法调用。这是我要解决的第一件事,否则在Apple的文档中警告说事情可能会出错。 (这并不意味着他们会出错,你这样做会更安全。)
完成此操作后,重要的是调用endUpdates
检查数据源,以确保通过调用numberOfRows
来计算任何插入或删除。例如,如果您删除一行(正如您所做),当您致电endUpdates
时,最好确保您还从数据源中删除了某个项目。看起来你正在做那个部分,因为你要删除self.messages
中的一个项目,我认为这是你的数据源。
(顺便提一下,你自己调用deleteRows...
而不在beginUpdates
/ endUpdates
包围,也可以在数据源上调用numberOfRows
,你可以轻松查看在其中加入一个断点。但是,请不要这样做,始终使用beginUpdates
/ endUpdates
。)
现在 可能在调用deleteRows...
和endUpdates
之间,其他人正在通过向其添加对象来修改self.messages
数组,但我不是&# 39;不知道我是否真的相信这一点,因为它必须非常不幸地计时。当您收到推送消息时,您不能正确处理行的插入,再次使用beginUpdates
/ endUpdates
或执行其他操作时更有可能错误。如果您可以发布处理插入的代码部分,将会很有帮助。
如果该部分看起来没问题,那么看起来你很遗憾在调用删除代码时对另一个线程上的self.messages
数组进行了更改。您可以通过添加日志行来检查此问题,其中插入代码会向数组添加消息:
NSLog(@"Running on %@ thread", [NSThread currentThread]);
或者只是在其中放置一个断点并查看最终结束的线程。如果这确实是问题,您可以通过执行以下操作来调度修改数组的代码并将行插入主线程(无论如何它应该是UI操作):
dispatch_async(dispatch_get_main_queue(), ^{
[self.messages addObject:newMessage];
[self.tableView beginUpdates];
[self.tableView insertRows...]
[self.tableView endUpdates];
});
这将确保在主线程忙于删除行时,消息数组不会被另一个线程变异。