3
votes

I have a table view that's backed by a fetchedResultsController. On my table cells I have a button that performs a soft delete, as far as the view is concerned... tap the button, the selector that performs the soft delete is called, and ... something is supposed to happen (I've tried a lot of things), but nothing I've tried animates like Apple's row animations.

I can perform simple row animations, like making it slide out of the way (which leaves a blank row until I tell the table to reload, which in turn is a bit of a jarring effect). This is the closest I've come:

 -(void) completeTask: (id) sender {
     NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]];

     NSDate *date = [[NSDate alloc] init];
     NSManagedObject *task = [self.fetchedResultsController objectAtIndexPath:indexPath];

     [task setValue:date forKey:@"complete"];

     AppController *ac = [AppController sharedAppController];
     NSManagedObjectContext *moc = [ac managedObjectContext];

     NSError *error = nil;
     if(![moc save:&error]){
         NSLog(@"%@ %@",error, [error description]);
         exit(-1);
     } else {
         UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

         CGRect cellFrame = cell.frame;
         [UIView beginAnimations:nil context:nil];
         [UIView setAnimationDuration:0.35];
         [UIView setAnimationDelegate:self];
         [UIView setAnimationDidStopSelector:@selector(someAnimationDidStop:finished:context:)];
         cell.frame = CGRectMake(cellFrame.size.width , cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height);
         [UIView commitAnimations];
     }
 }

-(void) someAnimationDidStop:(NSString *)animationID 
                    finished:(BOOL)finished context:(void *)duration {
    NSLog(@"Animation Stopped");
    [self fetchResults]; // this guy performs a new fetch and table data reload
}

I feel like I'm sort of on the right track, however I don't think this is really quite the answer. I was hoping that somehow controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: would be the answer. My guess is that if I want the stock Apple animations, I will have to create a separate NSMutableArray to track the results and just make sure it's kept in sync.

Thoughts and opinions?

Thanks!!!

2

2 Answers

2
votes

I guess you have already a predicate for the NSFetchedResultsController that doesn't fetch those "soft deleted" objects.

So in theory all you have to do is to implement all of the NSFetchResultsControllerDelegate methods. The sample implementation from the documentation will work fine.
Don't forget to set the delegate of the NSFetchedResultsController

Then instead of manually managing cells, just change the "soft delete" value from the object. The NSFetchResultsController will monitor the context, it figures out that this change make the object disappear from the collection so it will remove the cell from the tableView.

1
votes

EDIT: @fluchtpunkt answer is correct. After making a few more updates to my code my solution broke again. Removing the the block that re-fetches the FRC made it all work again.


I'm surprised I didn't figure this out in all my iterative testing (learning) yesterday. I was had run into problems with syncing the core data fetched results with the table view in a nice way - often I'd get the mis-match index (blah blah blah the number of rows (#) must equal the number # plus or minus the number of rows blah blah blah ( 0 inserted: # deleted).. etc).

My steps to complete this were as follows:

  1. Perform the soft delete & save
  2. set the fetched results controller to nil self.fetchedrResultsController = nil
  3. Do a new fetch
  4. THEN delete the row from the table:
         self.fetchedResultsController = nil;
         NSError *error = nil;
         if (![[self fetchedResultsController] performFetch:&error]) {
             NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
             abort();
         } else {
             [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];
         }

I really don't know why I didn't figure this out earlier, but it makes so much sense. Fresh eyes?

Here's the full picture:

 -(void) completeTask: (id) sender {

     NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]];

     NSManagedObject *task = [self.fetchedResultsController objectAtIndexPath:indexPath];

     [task setValue:date forKey:@"complete"];

     AppController *ac = [AppController sharedAppController];
     NSManagedObjectContext *moc = [ac managedObjectContext];

     NSError *error = nil;
     if(![moc save:&error]){
         NSLog(@"Uh oh - couldn't save the delete! %@ \n%@",error, [error description]);
         exit(-1);
     } else {

         // Save successful; re-fetch data (background)
         self.fetchedResultsController = nil;
         NSError *error = nil;
         if (![[self fetchedResultsController] performFetch:&error]) {
             NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
             abort();
         } else {
             //update the table view with row animation
             [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
                                   withRowAnimation:UITableViewRowAnimationRight];
         }
     }
 }