使用 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有三种方式(不使用,动态使用,静态使用,默认是动态使用)