Reputation: 3126
I wasn't able to find an answer for this specific problem so here it is.
I have multiple VC's under a UITabBarController, each has a UITableView and use an NSFetchedRequestController to get data. In each VC I have a refreshFetchedRequest method, which I call on viewDidAppear to start reflecting any change from one VC to another. It basically just does a performFetch.
This problem could be easily worked around by calling reloadData after refreshFetchedRequest, but I want row/section insertion/deletion animations.
So to summarize on viewDidAppear, I do:
[self refreshFetchedRequest];
[tableView beginUpdates];
// compare cached rows/sections with NSFetchedRequestController - how?
[tableView endUpdates];
Where do I get the existing cached UITableView rows and sections to compare them with the refreshed NSFetchedResultsController?
UPDATE:
I am now doing this in my viewDidAppear method:
NSFetchedRequestController *frcBeforeUpdate = frc;
[self refreshFetchedRequest]; // refreshes my frc property
[tableView beginUpdates];
// compare frcBeforeUpdate with frc here
[tableView endUpdates];
Upvotes: 0
Views: 252
Reputation: 3126
- (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];
}
Upvotes: 0
Reputation: 3763
Ok, after re-reading you question, I think I now understand what you want: you want the tableview to update with animations, AFTER it has appeared again. So you want to delay the updates to the tableView.
Use NSFetchedResultsController. Instead of calling the relevant update-method on your tableView in respons to those methods, store the changes in an NSDictionary, or a custom class, where you store the index paths for the type of changes. Then in viewDidAppear, you are going to call something like:
[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;
Is this what you were looking for?
Upvotes: 1
Reputation: 472
You should to become a delegate NSFetchedResultsController and process delegate's methods following way:
- (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];
}
Also you can see a good example of adding new records to a table across NSFetchedResultsController by creating an empty Master-Detail Application project on the Xcode.
Upvotes: 0
Reputation: 3763
Your problem is then solved by making sure that either:
In both cases you will get the delegate callbacks you are looking for.
Upvotes: 1
Reputation: 3763
You have to set the viewController as a delegate of the fetchedResultsController. Check the documentation on NSFetchedResultsController, it shows you copy-and-pastable code to do exactly what you want.
Upvotes: 0