使用 NSFetchedRequestController 更新 UITableView 行和部分,而不使用 reloadData

Posted

技术标签:

【中文标题】使用 NSFetchedRequestController 更新 UITableView 行和部分,而不使用 reloadData【英文标题】:update UITableView rows and sections using NSFetchedRequestController and without reloadData 【发布时间】:2014-07-08 06:30:19 【问题描述】:

我无法找到这个特定问题的答案,所以在这里。

我在一个 UITabBarController 下有多个 VC,每个都有一个 UITableView 并使用 NSFetchedRequestController 来获取数据。在每个 VC 中,我都有一个 refreshFetchedRequest 方法,我调用 viewDidAppear 以开始反映从一个 VC 到另一个 VC 的任何更改。它基本上只是做一个 performFetch。

这个问题可以通过在 refreshFetchedRequest 之后调用 reloadData 轻松解决,但我想要行/节插入/删除动画。

所以总结一下 viewDidAppear,我这样做:

[self refreshFetchedRequest];
[tableView beginUpdates];
// compare cached rows/sections with NSFetchedRequestController - how?
[tableView endUpdates];

我从哪里获取现有缓存的 UITableView 行和部分以将它们与刷新的 NSFetchedResultsController 进行比较?

更新:

我现在在我的 viewDidAppear 方法中这样做:

NSFetchedRequestController *frcBeforeUpdate = frc;
[self refreshFetchedRequest]; // refreshes my frc property
[tableView beginUpdates];
// compare frcBeforeUpdate with frc here
[tableView endUpdates];

【问题讨论】:

【参考方案1】:

然后通过确保以下任一方式解决您的问题:

两个 viewController 使用相同的 managedObjectContext 您侦听 managedObjectContextDidSave 通知并在用于 fetchedResultsController 的 managedObject 上调用 mergeChangesFromManagedObjectContextDidSaveNotification。

在这两种情况下,您都会得到您正在寻找的委托回调。

【讨论】:

这 2 个 VC 使用相同的 moc,知道何时查找更改不是问题,因为我是异步执行的。 如果你使用 MOC 来更新你的 UI,你需要一个带有 concurrencyTypeMainQueue 的 MOC。如果两个 viewController 已经使用相同的 MOC,你只需要实现 NFRC-delegate 方法。或者我不了解您的结构,并希望提供更好的答案。 实际上我不希望这里有任何委托回调 为什么不呢?您有一个 NSFetchedRC,因此如果您将视图控制器设置为委托,当 managedObjectContext 发生更改时,您将获得委托回调。 managedObjects 更改的 MOC 必须是您用来设置 NSFRC 的 MOC。 我想我在 OP 中没有明确表达。但我想对 viewDidAppear 执行单个批处理表插入/删除更新,并且我有一个 FRC 结构。实现 FRC 委托方法最多会给我回调,因为 UITabBarController 的其他 VC 进行了更改。【参考方案2】:

好的,在重新阅读您的问题之后,我想我现在明白了您想要什么:您希望表格视图在它再次出现之后更新动画。所以你想延迟对 tableView 的更新。

使用 NSFetchedResultsController。与其在 tableView 上调用相关的更新方法来响应这些方法,不如将更改存储在 NSDictionary 或自定义类中,在其中存储更改类型的索引路径。然后在 viewDidAppear 中,您将调用类似:

[tableView beginUpdates]
for (NSIndexPath * anIndexPath in self.cachedChanges[updates])
    [tableView reloadRowAtIndepath: anINdexPath];

for (NSIndexPath * indexPath in self.cachedChanges[deletions])
    [tableView deleteRowAtindexPath: indexPath]

// ... more like this for sections and insertions
[tableView endUpdates];
self.cachedChanges = nil;

这是你要找的吗?

【讨论】:

我复制了我想给你看的东西:P 我想来回帮助我更好地理解它 那么这个答案对你有帮助吗,还是还有什么不清楚的地方? 你想让我给你代表并标记正确答案吗? 不,没关系,如果我现在理解你的问题并且我的建议实际上是在回答它,我只是在徘徊:)。 我认为你比你建议使用 NSFetchedResultsControllerDelegate 时更接近,尽管我最初被卡住的地方是认为可能有一个缓存(就像你在上面的代码中建议的那样)我可以用来在我调用 performFetch 之后比较我的 NSFetchedResultsController 以包含我需要的所有批量更新。我需要做的就是在 performFetch 调用之前保存 NSFetchedResultsController 状态,然后在调用表插入和删除之后比较节和行。我对你所有的答案都投了赞成票,因为它们帮助我分析了问题。谢谢!【参考方案3】:

您必须将 viewController 设置为 fetchedResultsController 的委托。查看 NSFetchedResultsController 上的文档,它向您展示了复制和粘贴代码以完全按照您的意愿行事。

【讨论】:

我想我仍然有一个问题,即更改发生在不同的 VC 上。这就是我在 viewDidAppear 方法中调用 UITableView 更新块的原因。 我很确定这行不通。 NSFetchedResultsControllerDelegate 适用于不需要调用 performFetch 来同步 UITableViewDataSource 的情况。由于更改是在另一个 VC 上进行的,因此不会调用 NSFetchedResultsControllerDelegate 方法。本质上,我的问题是需要知道如何编写批量更新,而使用 NSFetchedResultsControllerDelegate 是为了以某种同步的方式进行个别更新。【参考方案4】:

你应该成为一个委托 NSFetchedResultsController 并通过以下方式处理委托的方法:

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


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type

    UITableViewRowAnimation animationType = UITableViewRowAnimationAutomatic;
    switch(type) 
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:animationType];
            break;

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


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

    UITableViewRowAnimation animationType = UITableViewRowAnimationFade;
    UITableView *tableView = self.tableView;

    switch(type) 
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:animationType];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:animationType];
            break;

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

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:animationType];
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:animationType];
            break;
    


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

您还可以通过在 Xcode 上创建一个空的 Master-Detail Application 项目来跨 NSFetchedResultsController 将新记录添加到表中的一个很好的示例。

【讨论】:

【参考方案5】:
- (void)animateRefreshFetchedResultsControllers

    NSFetchedResultsController *frc3 = _frc1;
    [self refreshFetchedResultsControllers];

    NSMutableArray *oldSectionNames = [NSMutableArray new];
    NSMutableArray *newSectionNames = [NSMutableArray new];
    for (id <NSFetchedResultsSectionInfo> section in frc3.sections) 
        [oldSectionNames addObject:section.name];
    
    for (id <NSFetchedResultsSectionInfo> section in _frc1.sections) 
        [newSectionNames addObject:section.name];
    

    [_tableView beginUpdates];
    NSMutableIndexSet *sectionsToDelete = [NSMutableIndexSet new];
    for (NSString *sectionName in oldSectionNames) 
        if (![newSectionNames containsObject:sectionName]) 
            [sectionsToDelete addIndex:[oldSectionNames indexOfObject:sectionName]];
        
    
    [_tableView deleteSections:sectionsToDelete withRowAnimation:UITableViewRowAnimationLeft];

    NSMutableIndexSet *sectionsToInsert = [NSMutableIndexSet new];
    for (NSString *sectionName in newSectionNames) 
        if (![oldSectionNames containsObject:sectionName]) 
            [sectionsToInsert addIndex:[newSectionNames indexOfObject:sectionName]];
        
    
    [_tableView insertSections:sectionsToInsert withRowAnimation:UITableViewRowAnimationRight];

    NSMutableArray *rowsToInsert = [NSMutableArray new];
    for (int i = 0; i < sectionsToInsert.count; i++) 
        id <NSFetchedResultsSectionInfo> section = _frc1.sections[i];
        for (int j = 0; j < section.numberOfObjects; j++) 
            NSIndexPath *rowIndexPath = [NSIndexPath indexPathForRow:j inSection:i];
            [rowsToInsert addObject:rowIndexPath];
        
    
    [_tableView insertRowsAtIndexPaths:rowsToInsert withRowAnimation:UITableViewRowAnimationRight];

    [_tableView endUpdates];

【讨论】:

以上是关于使用 NSFetchedRequestController 更新 UITableView 行和部分,而不使用 reloadData的主要内容,如果未能解决你的问题,请参考以下文章

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?

今目标使用教程 今目标任务使用篇

Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)

MySQL db 在按日期排序时使用“使用位置;使用临时;使用文件排序”

使用“使用严格”作为“使用强”的备份

Kettle java脚本组件的使用说明(简单使用升级使用)