JackyJohnson
JackyJohnson

Reputation: 3126

update UITableView rows and sections using NSFetchedRequestController and without reloadData

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

Answers (5)

JackyJohnson
JackyJohnson

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

Joride
Joride

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

d0ping
d0ping

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

Joride
Joride

Reputation: 3763

Your problem is then solved by making sure that either:

  • the two viewControllers use the same managedObjectContext
  • you listen for managedObjectContextDidSave notification and call mergeChangesFromManagedObjectContextDidSaveNotification on the managedObject you use for the fetchedResultsController.

In both cases you will get the delegate callbacks you are looking for.

Upvotes: 1

Joride
Joride

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

Related Questions