0

I have a table view backed up by core data with NSFetchedResultsController instance. This table has a search bar that displays filtered data. The search bar has a separate NSFetchedResultsController (FRC from now) instance.

So far so good, the data is fetched and shown as expected in the table view and also shown correctly in the search bar's table view (when searching for data).

My problem is that if I try to delete a cell in the search bar's table view then I get a coredata exception :

error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 0 from section 0 which only contains 0 rows before the update with userInfo (null)

Further examination shows that the FRC's controllerWillChangeContent method is called twice on one cell deletion!. This causes deleteRowsAtIndexPaths to be called twice for the same cell (thus the coredata exception).

Playing with it some more I have found out that this problem happens from the first time the search bar's table view is shown after a search. (Even when I go back and delete the cell in the regular table view the problem occurs)

I've made sure that the deleteRowsAtIndexPaths method is called only once in the code (in the FRC's didChangeObject delegate method).

Now I am not sure which code to show so I won't just spill it all. let me know which one you need to look at if you have an idea on what the problem is.

This is the code where the table is instructed to delete a row :

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

    DLog(@"didChangeObject type %lu with object %@", (unsigned long)type, anObject);
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

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

        case NSFetchedResultsChangeMove:
            [self.tableView deleteRowsAtIndexPaths:[NSArray
                                               arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView insertRowsAtIndexPaths:[NSArray
                                               arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

This method is called when I save the context after I delete the object for the selected cell.

The method where I save the context:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [_managedObjectContext deleteObject:[_notesFetcher objectAtIndexPath:indexPath]];
        NSError *error;
        if (![_managedObjectContext save:&error]) {
            DLog(@"Failed deleting object : %@", [error localizedDescription]);
        }
    }   
}

And for some reason this method is called once but invokes didChangeObject twice with the same change type (2-Delete) for the same cell.

UPDATE

When I log for the stack trace in the method didChangeObject I get this:

[UITableView animateDeletionOfRowWithCell:] + 107 12 UIKit
0x01316a35 -[UITableViewCell _swipeDeleteButtonPushed] + 70 13 libobjc.A.dylib 0x02346874 -[NSObject performSelector:withObject:withObject:] + 77 14 UIKit
0x010a8c8c -[UIApplication sendAction:to:from:forEvent:] + 108 15 UIKit 0x010a8c18 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61 16 UIKit
0x011a06d9 -[UIControl sendAction:to:forEvent:] + 66 17 UIKit
0x011a0a9c -[UIControl _sendActionsForEvents:withEvent:] + 577 18 UIKit 0x0119fd4b -[UIControl touchesEnded:withEvent:] + 641 19 UIKit
0x0141ad7f _UIGestureRecognizerUpdate + 7166 20 UIKit
0x010e5d4a -[UIWindow _sendGesturesForEvent:] + 1291 21 UIKit
0x010e6c6a -[UIWindow sendEvent:] + 1030 22 UIKit
0x010baa36 -[UIApplication sendEvent:] + 242 23 UIKit
0x010a4d9f _UIApplicationHandleEventQueue + 11421

As you can see the table's swipeDeleteButtonPushed method is called twice on one delete operation as I described above. How can this be triggered twice when I only pushed the delete button once ? any ideas ?

giorashc
  • 13,691
  • 3
  • 35
  • 71
  • Can you show the code where the table view row is deleted? – Martin R Dec 26 '13 at 21:01
  • My guess would be you are using 2 FRC's with the same controller for both search and regular view. in your "update" method you are updating the same table twice. – Dan Shelly Dec 26 '13 at 21:08
  • @MartinR I added the relevant where deletion occurs – giorashc Dec 26 '13 at 21:26
  • @DanShelly Indeed I use the same controller as the delegate for both FRCs but I do use the right one when needed (searches and selecting cells invokes the right segue and the correct data is displayed) Somewhere I read that `self.tableView` always points to the current table view (i.e. if the search bar's table view is displayed then `self.tableView` will point to that table) and should be used in the table's delegate methods. – giorashc Dec 26 '13 at 21:28
  • @giorashc: That is not correct. You have to update `self.tableView` (which is always the "main" table view) or `self.searchDisplayController.searchResultsTableView`, depending on whether a search is active or not. So @Dan seems to be on the right track. – Martin R Dec 26 '13 at 21:39
  • @giorashc: The `tableView` argument of the data source or delegate methods will always point to the right table view, but not `self.tableView`. – Martin R Dec 26 '13 at 21:41
  • @MartinR this is where I read about using self.tableView : http://stackoverflow.com/a/14221987/986169. Although it is just refers to dequeue I'll still try using the current tableView and see how it goes... – giorashc Dec 27 '13 at 17:16
  • @DanShelly you were right. I used a wrong if statement which resulted in using the same table twice instead of the main table and the search table. thanks for the help to all, this was silly but tough one to spot – giorashc Dec 28 '13 at 19:02

1 Answers1

1

You need to always check which controller is calling your delegate method and act accordingly. Similarly you need to check which tableView is calling your delegate method. Assuming that your viewController is the delegate for both the search fetchedresultscontroller and the normal fetchedresultscontroller as well as the search tableView and the normal tableView.

So you need something,like

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{

    If (tableView == searchTableView) {
      // do stuff to searchTableView

    } else  {
      // do stuff to normalTableView

    }
}

Or in the fetchedResultsController delegate methods (and I am pretty sure this will be where you are getting two calls acting on the same tableView) something like

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

    If (controller == searchFetchedResultsController) {
       // do stuff to searchTableview

    } else {
       // do stuff to normaltableview

    }
}
Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76
  • I do check which table view is shown for using the right FRC. Even when I delete from the main table view (after searching and going back to the main table and delete the cell) this problem happens – giorashc Dec 27 '13 at 17:52
  • Are you sure you are not deleting it from both table views by mistake, you should be able to delete from the search table view and it should be removed from the main table view automatically. – Duncan Groenewald Dec 28 '13 at 13:47
  • 1
    Remember when you delete the object both fetchedresultscontrollers will call their delegate methods, which seems to be exactly what you describe above, but your code is not checking which fetchedresultscontroller is calling the delegate method. – Duncan Groenewald Dec 28 '13 at 13:50
  • Your didChangeObject method listing above does not check which fetchedResultsController is calling the delegate method – Duncan Groenewald Dec 28 '13 at 13:53
  • Wow, I was sure I was doing it right. Instead of ' If (controller == searchFetchedResultsController)' I checked if the search bar controller is active. Soooo wrong. Seems that the same tableview was called twice! instead one for the main tableview and another for the search tableview as you described. Your last comments helped me reaching this fact. Thanks! – giorashc Dec 28 '13 at 15:59