如何从NSFetchedResultsControllerDelegate实现延迟/批量表视图更新?

时间:2010-02-02 07:32:10

标签: iphone core-data nsfetchedresultscontroller

The documentation says:

  

您应该仔细考虑是否要在每次更改时更新表视图。如果同时进行大量修改 - 例如,如果您正在从后台线程读取数据 - /.../,您可以实现controllerDidChangeContent :(在处理完所有挂起的更改时发送给委托)到重新加载表视图。

这正是我正在做的事情:我正在使用不同的ManagedObjectContext处理后台线程中的传入更改,并使用mergeChangesFromContextDidSaveNotification将结果合并到主线程MOC中。到目前为止一切都很好。

我选择不实现controller:didChangeObject:...而是希望执行文档建议的批量更新。

问题/问题:该文档没有详细说明如何实际实施批量更新?我应该在controllerDidChangeContent中调用[tableview reloadData]:还是有一种不那么干扰的方式可以让我免于完全重载?

我有一个想法:我可以注意到mergeChangesFrom ...包含已更改对象的通知,找出它们的索引路径,然后只调用tableview:ReloadRowsAtIndexPaths:for。但是有任何权威信息,建议或例子吗?或者只是[tableview reloadData]?

(旁白:控制器:didChangeObject:...当它收到一组批量更新时开始表现得非常不稳定,即使相同的更新代码[我现在放在后台线程中]在它运行之前运行良好主线程,但当然锁定UI。)

3 个答案:

答案 0 :(得分:1)

我只需在reloadData中致电controllerDidChangeContent:

为了动画表的个别更改,Apple的样板代码(iOS SDK 4.3.x)如下所示:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;

    switch(type)
    {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}

答案 1 :(得分:1)

我相信这就是你要找的东西:

而不是打电话

  • deleteSections
  • insertSections
  • reloadSections
  • deleteRowsAtIndexPaths
  • insertRowsAtIndexPaths
  • reloadRowsAtIndexPaths

当更改进入controller:didChangeObject:atIndexPath:forChangeType:newIndexPath时,收集属性中的indexPaths和section。然后在controllerDidChangeContent:

中执行批处理

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

修改

以下是链接中提到的方法略有不同,更加人为但又更紧凑的版本:

@property (nonatomic, strong) NSMutableArray *sectionChanges;
@property (nonatomic, strong) NSMutableArray *objectChanges;

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{

}

- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{
    NSMutableDictionary *sectionChange = [NSMutableDictionary new];

    switch(type)
    {
        case NSFetchedResultsChangeInsert:
        case NSFetchedResultsChangeDelete:
        case NSFetchedResultsChangeUpdate:
            sectionChange[@(type)] = @(sectionIndex);
            break;
    }

    [self.sectionChanges addObject:sectionChange];
}

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    NSMutableDictionary *objectChange = [NSMutableDictionary new];

    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            objectChange[@(type)] = newIndexPath;
            break;
        case NSFetchedResultsChangeDelete:
        case NSFetchedResultsChangeUpdate:
            objectChange[@(type)] = indexPath;
            break;
        case NSFetchedResultsChangeMove:
            objectChange[@(type)] = @[indexPath, newIndexPath];
            break;
    }

    [self.objectChanges addObject:objectChange];        
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    UITableView *tableView = self.tableView;

    NSUInteger totalChanges = self.sectionChanges.count + self.objectChanges.count;

    if (totalChanges > 0)
    {
        [tableView beginUpdates];

        if (self.sectionChanges.count > 0)
        {
            for (NSDictionary *change in self.sectionChanges)
            {
                [change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {

                    NSFetchedResultsChangeType type = [key unsignedIntegerValue];

                    switch (type)
                    {
                        case NSFetchedResultsChangeInsert:
                            [tableView insertSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]] withRowAnimation:UITableViewRowAnimationAutomatic];
                            break;
                        case NSFetchedResultsChangeDelete:
                            [tableView deleteSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]] withRowAnimation:UITableViewRowAnimationAutomatic];
                            break;
                        case NSFetchedResultsChangeUpdate:
                            [tableView reloadSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]] withRowAnimation:UITableViewRowAnimationAutomatic];
                            break;
                    }
                }];
            }
        }
        else if (self.objectChanges > 0)
        {
            NSMutableArray *indexPathsForUpdatedObjects = [NSMutableArray new];

            for (NSDictionary *change in self.objectChanges)
            {
                [change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {

                    NSFetchedResultsChangeType type = [key unsignedIntegerValue];
                    switch (type)
                    {
                        case NSFetchedResultsChangeInsert:
                            [tableView insertRowsAtIndexPaths:@[obj] withRowAnimation:UITableViewRowAnimationAutomatic];
                            break;
                        case NSFetchedResultsChangeDelete:
                            [tableView deleteRowsAtIndexPaths:@[obj] withRowAnimation:UITableViewRowAnimationAutomatic];
                            break;
                        case NSFetchedResultsChangeUpdate:
                            [indexPathsForUpdatedObjects addObject:obj];
                            //[tableView reloadRowsAtIndexPaths:@[obj] withRowAnimation:UITableViewRowAnimationAutomatic];
                            break;
                        case NSFetchedResultsChangeMove:
                            [tableView moveRowAtIndexPath:obj[0] toIndexPath:obj[1]];
                            break;
                    }
                }];
            }

            [tableView endUpdates];

            for (NSIndexPath *indexPath in indexPathsForUpdatedObjects)
                if ([tableView.indexPathsForVisibleRows containsObject:indexPath])
                    [self updateCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
        }

        [self.sectionChanges removeAllObjects];
        [self.objectChanges removeAllObjects];

    }
    else if (totalChanges < 1)
    {
        [tableView reloadData];
    }
}

- (NSMutableArray *)sectionChanges
{
    if (!_sectionChanges)
        _sectionChanges = [NSMutableArray new];

    return _sectionChanges;
}
- (NSMutableArray *)objectChanges
{
    if (!_objectChanges)
        _objectChanges = [NSMutableArray new];

    return _objectChanges;
}

请注意,此解决方案并不完美,因为如果对部分进行更改并且这些对象位于未更新的部分中,则不会更新对象。应该很容易修复(并且与许多应用程序无关)。

答案 2 :(得分:0)

您可以记下数据源中的最后一个索引路径&amp;然后这样做 -

    NSIndexPath *indexPath     = nil;
    NSMutableArray *newResults = [[NSMutableArray alloc] init];
    if(gLastResultIndex < [self.dataSource count])
    {
        for(int i=(gLastResultIndex); i<[self.dataSource count]; i++)
        {
            indexPath = [NSIndexPath indexPathForRow:i inSection:0];
            [newResults addObject:indexPath];
        }
        [self.table beginUpdates];
        [self.table insertRowsAtIndexPaths:newResults withRowAnimation:UITableViewRowAnimationNone];
        [self.table endUpdates];
    }
    [newResults release];

这有选择地向现有UITableView添加新行。重装表重新加载表中您可能不需要的所有单元格。我只在整个数据源发生变化时使用重载表...