核心数据支持UITableView在删除单元格时出错

时间:2014-02-17 01:32:47

标签: ios objective-c uitableview core-data magicalrecord

我有一个由Core Data对象数组支持的UITableView,它是通过附加两个名为folders和notes的Core Data对象数组来实现的。每当我尝试从表视图中删除一行时,它都会抛出:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

这是要删除的代码(在我的UITableViewCell子类中):

NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
[object MR_deleteInContext:context];
[context MR_saveToPersistentStoreAndWait];
[tableViewController loadData];
[tableView deleteRowsAtIndexPaths:@[self.indexPath] withRowAnimation:UITableViewRowAnimationLeft];

注意:MR_方法是MagicalRecord方法,他们按照他们的意思行事。

这是UITableViewController中的loadData方法(基本上是重新获取数组):

NSPredicate *textParentFilter = [NSPredicate predicateWithFormat:@"parent == %@", self.parent];
self.notes = [Note MR_findAllWithPredicate:textParentFilter];
NSPredicate *folderParentFilter = [NSPredicate predicateWithFormat:@"parentFolder == %@", self.parent];
self.folders = [Folder MR_findAllWithPredicate:folderParentFilter];

此外:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    return [self.notes count] + [self.folders count];
}

我可以看到为什么我得到一个异常:我正在删除loadData中的行,然后numberOfRowsInSection与tableview和Core Data存储不同步。但是,如果我用[tableView reloadData]替换deleteRowsAtIndexPaths,它删除就好了(但没有动画)。我尝试过beginUpdates和endUpdates,切换删除内容的顺序无济于事。

3 个答案:

答案 0 :(得分:3)

使用UITableViews和Core Data时,重要的是存储中的对象(例如,您的注释和文件夹数组)始终与UITableView委托/数据源调用匹配。当您开始动画单元格以进行添加,删除和更新时,会遇到同步问题,有时候这些值不对齐。一个很好的例子是当你必须连续执行多个单元格添加/删除时,同时显示或显示表格的一部分。如果使用[tableView reloadData],则会强制执行同步,但无法为单元格设置动画。

NSFetchedResultsController可用于将多个细胞添加/删除与动画同步。这将需要你的一些重新布线,但没有你想象的那么多。有了这个,您可以控制动画样式,甚至批量修改单元格,同时保持表格完好无损。 NSFetchedResultsController将根据谓词侦听Core Data中的更改,并在tableview中反映这些更改。这意味着您可以让多个tableviews使用自己的NSFetchedResultsController进行响应,而无需通知它们,并且所有内容都将保持同步。

查找框架代码的好地方是在XCode中设置新的Core Data项目。您需要添加到代码中的方法(在MasterViewController.m中找到):

// this references your controller that listens to Core Data and communicates with the tableview
- (NSFetchedResultsController *)fetchedResultsController 

// delegate methods:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
       atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
   atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath *)newIndexPath;
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;

然后,您的tableview将需要查询NSFetchedController实例以获取它的委托/数据源方法。例如:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
    return [sectionInfo numberOfObjects];
}

如果您感觉足够先进,那么有一个很棒的教程可以显示如何排列细胞添加/删除以提高性能:

http://www.fruitstandsoftware.com/blog/2013/02/uitableview-and-nsfetchedresultscontroller-updates-done-right/

NSFetchedResultsController是您在基本核心数据教程中没有真正听到过的内容之一,但在进行表格动画和处理大量数据时(通过缓存),它被证明是真正的资产。

答案 1 :(得分:0)

每当用户滑动单元格以删除它时,我都会使用以下代码:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        if (tableView == self.tableView)
        {
           NSInteger row = [indexPath row];
           Entry *e = self.entries[row];

           [self.entries removeObjectAtIndex: indexPath.row];

           [e MR_deleteEntity];
           [self.tableView reloadData];
        }
    }
}

在您的情况下,首先需要确定“行”是否与NoteFolder相对应,但这个想法是相同的。

答案 2 :(得分:0)

我修好了!或者更确切地说,NSFetchedResultsController修复了它。我已经实施了

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath

在我的视图控制器中deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:。我的删除方法现在只是

NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext]; [object MR_deleteInContext:context]; [context MR_saveToPersistentStoreAndWait];

(感谢有关MR_contextForCurrentThread的建议)。

这里有loadData:

NSManagedObjectContext *c = [NSManagedObjectContext MR_defaultContext]; NSPredicate *textParentFilter = [NSPredicate predicateWithFormat:@"parent == %@", self.parent]; self.notes = [Note MR_fetchAllSortedBy:@"text" ascending:YES withPredicate:textParentFilter groupBy:nil delegate:self inContext:c]; NSPredicate *folderParentFilter = [NSPredicate predicateWithFormat:@"parentFolder == %@", self.parent]; self.folders = [Folder MR_fetchAllSortedBy:@"title" ascending:YES withPredicate:folderParentFilter groupBy:nil delegate:self inContext:c];

和 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section:

if ([self.notes.sections count] + [self.folders.sections count] > 0) { id <NSFetchedResultsSectionInfo> notesSectionInfo = [self.notes.sections objectAtIndex:section]; id <NSFetchedResultsSectionInfo> foldersSectionInfo = [self.folders.sections objectAtIndex:section]; return [notesSectionInfo numberOfObjects] + [foldersSectionInfo numberOfObjects]; } else return 0;