views:

1533

answers:

1

Hi guys - another weird problem with the iPhone SDK here.

I have a UITableView which contains a custom UITableViewCell. This table gets its data from a mutable array, which in turn pulls it from a Core Data storage.

The problem: If I enter editing mode and delete a cell, it works for the first time - the cell is successfully removed from the UITableView and the Core Data database. However, if I attempt to delete another cell, the application crashes after clicking the Delete button for the row (the one that actually fires the action, not the button that rotates itself to reveal its partner).

This is my code for the UITableView delegate method corresponding to the deletion of rows:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if ( editingStyle == UITableViewCellEditingStyleDelete ) {
        NSManagedObject *dream = [dreamsArray objectAtIndex:indexPath.row]; 
        [managedObjectContext deleteObject:dream];

        [dreamsArray removeObjectAtIndex:indexPath.row];
        // removing from instance field array works fine, but...
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        // ... crash right here

        NSError *error = nil;
        if ( ![[self managedObjectContext] save:&error] )
            NSLog(@"DreamsViewController:140 %@", error);
    }
}

I tracked down the problematic method to find deleteRowsAtIndexPaths:withRowAnimation: was (surprisingly) the culprit. I have tried exiting the editing mode, visiting another view, and revisiting this table to delete another, but this didn't work, either.


Update: I have added a test to ensure that an incorrect row value is not given to the UITableView after deleting an element, causing it to crash. Modified code:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if ( editingStyle == UITableViewCellEditingStyleDelete ) {
        NSManagedObject *dream = [dreamsArray objectAtIndex:indexPath.row];
        [managedObjectContext deleteObject:dream];

        // outputs right before a cell is deleted
        NSLog(@"before\tarray\t%d", [dreamsArray count]);

        [dreamsArray removeObjectAtIndex:indexPath.row];
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

        NSError *error = nil;
        if ( ![[self managedObjectContext] save:&error] )
            NSLog(@"DreamsViewController:129 %@", error);
    }
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // outputs after a cell is deleted
    NSLog(@"after\tarray\t%d", [dreamsArray count]);
    return [dreamsArray count];
}

The debug console showed no sort of logic error with the cell's removal (and the removal of its corresponding object):

** pressed "Delete" for the first time **
2009-09-21 16:47:12.567 xxx[774:20b] before array   11
2009-09-21 16:47:12.568 xxx[774:20b] after  array   10

** pressed "Delete" on another cell after first deletion finished **
2009-09-21 16:47:17.685 xxx[774:20b] before array   10
2009-09-21 16:47:17.686 xxx[774:20b] after  array   9
** after this output, CRASH occurs **


Update: Here is the stack trace:

#0  0x94292688 in objc_msgSend ()
#1  0x016c867d in -[UITableView dequeueReusableCellWithIdentifier:] ()
#2  0x000038c4 in -[DreamsViewController tableView:cellForRowAtIndexPath:] (self=0x3e1e380, _cmd=0x29cfb18, tableView=0x401b800, indexPath=0x3e4ece0) at /Users/hans/Projects/xxx/Classes/DreamsViewController.m:85
#3  0x016cee60 in -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:withIndexPath:] ()
#4  0x016c6a97 in -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:] ()
#5  0x01866a18 in -[_UITableViewUpdateSupport(Private) _setupAnimationsForExistingOffscreenCells] ()
#6  0x01869a28 in -[_UITableViewUpdateSupport initWithTableView:updateItems:oldRowData:newRowData:oldRowRange:newRowRange:context:] ()
#7  0x016d6a2b in -[UITableView(_UITableViewPrivate) _updateWithItems:withOldRowData:oldRowRange:newRowRange:context:] ()
#8  0x016d6757 in -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] ()
#9  0x016c8a77 in -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] ()
#10 0x00003ec3 in -[DreamsViewController tableView:commitEditingStyle:forRowAtIndexPath:] (self=0x3e1e380, _cmd=0x29cfaa0, tableView=0x401b800, editingStyle=UITableViewCellEditingStyleDelete, indexPath=0x3e4e380) at /Users/hans/Projects/xxx/Classes/DreamsViewController.m:125
#11 0x016c66fc in -[UITableView(UITableViewInternal) animateDeletionOfRowWithCell:] ()
#12 0x0167f459 in -[UIApplication sendAction:to:from:forEvent:] ()
#13 0x016e2ba2 in -[UIControl sendAction:to:forEvent:] ()
#14 0x016e4dc3 in -[UIControl(Internal) _sendActionsForEvents:withEvent:] ()
#15 0x016e3b0f in -[UIControl touchesEnded:withEvent:] ()
#16 0x01698e33 in -[UIWindow _sendTouchesForEvent:] ()
#17 0x0168281c in -[UIApplication sendEvent:] ()
#18 0x016890b5 in _UIApplicationHandleEvent ()
#19 0x0001def1 in PurpleEventCallback ()
#20 0x00843b80 in CFRunLoopRunSpecific ()
#21 0x00842c48 in CFRunLoopRunInMode ()
#22 0x0001c7ad in GSEventRunModal ()
#23 0x0001c872 in GSEventRun ()
#24 0x0168a003 in UIApplicationMain ()
#25 0x00005461 in main (argc=1, argv=0xbfffef54) at /Users/hans/Projects/xxx/main.m:14

So, after looking through the stack trace, it looks like dequeueReusableCellWithIdentifier: has something to do with this problem, too. Here is the code that calls that method, though I don't see anything wrong with this, either..

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"dreamCell";

    DreamTableCell *cell = (DreamTableCell *)[tableView dequeueReusableCellWithIdentifier:cellID];
    if ( cell == nil )
        cell = [[[DreamTableCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellID] autorelease];

    Dream *dream = [dreamsArray objectAtIndex:indexPath.row];
    [cell dreamLabel].text = [dream dreamContent];

    [dateFormatter setDateFormat:@"dd"];
    cell.dayLabel.text = [dateFormatter stringFromDate:[dream date]];

    [dateFormatter setDateFormat:@"HH:mm"];
    cell.timeLabel.text = [dateFormatter stringFromDate:[dream date]];

    return cell;
}
+1  A: 

It's likely that an NSInternalInconsistencyException is being raised (check your console).

You need to ensure that when tableView:numberOfRowsInSection: is called on your UITableViewDelegate, the count is correct for the section of the cell that was deleted via deleteRowsAtIndexPaths:withRowAnimation:. i.e. If there were 10 elements in section 0 and you delete one of them via deleteRowsAtIndexPaths:withRowAnimation:, tableView:numberOfRowsInSection: must return 9 for section 0.

Nathan de Vries
I've tested for that, but it doesn't seem to be the problem - please see the updated post.
hansengel
You'll need to be more specific about the crash that's occurring (stack-trace, console output etc.).
Nathan de Vries
I'm sorry, I should've thought to include that earlier..I've updated the original post with the stack trace. There is no error, however, output to the console.Anyway.. looks like the culprit is `UITableView dequeueReusableCellWithIdentifier:`. I searched a bit on Google for any previous encounters with this method, but couldn't find anything..I've updated the original post, also, with the method that is calling `dequeueReusableCellWithIdentifier:`.
hansengel
Hm, well, I'm not too sure what happened there, but it seems to have fixed itself somehow. I'll post again if the problem returns.
hansengel