Search code examples
iosuitableviewreuseidentifier

Deleting multiple selections from UITableView throws exception after scrolling (reuse issue?)


I'm trying to delete multiple selections from a table view. Everything works fine until I scroll up or down, then it crashes and throws an exception.

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

Object can not be nil

This is how I am deleting the objects :

- (IBAction)deleteObjects:(id)sender {

    NSArray *selectedRows = [self.tableView indexPathsForSelectedRows];

    BOOL deleteSpecificRows = selectedRows.count > 0;
    if (deleteSpecificRows)
    {
        NSMutableArray *stringsOfObjects = [NSMutableArray new];
        for (NSIndexPath *selectionIndex in selectedRows) {
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:selectionIndex];
            [stringsOfObjects addObject:cell.textLabel.text];
        }

        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *path = [paths objectAtIndex:0];
        NSString *plistPath = [path stringByAppendingPathComponent:@"AlertSubscriptions.plist"];
        NSMutableArray *array = [[[NSMutableArray alloc] initWithContentsOfFile:plistPath] mutableCopy];

        [array removeObjectsInArray:stringsOfObjects];
        [self.alertSubArray removeObjectsInArray:stringsOfObjects];
        [array writeToFile:plistPath atomically:YES];

     [self.tableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationAutomatic];
    }

Again this all works fine, unless I scroll up/down to select/deselect more cells so I subclassed my cells because I read that won't reuse cells on SO.

For reference:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

SubscriptionsTableViewCell *cell = [tableView
                              dequeueReusableCellWithIdentifier:nil];
if (cell == nil) {
    cell = [[SubscriptionsTableViewCell alloc]
            initWithStyle:UITableViewCellStyleDefault
            reuseIdentifier:nil];
}

    cell.textLabel.text = [self.alertSubArray objectAtIndex:indexPath.row];
    cell.textLabel.textAlignment = NSTextAlignmentLeft;

    return cell;
}

Ive tried it with a static cell and without. I've tried setting dequeueReusableCellWithIdentifier to a static cell and without. Neither work when I scroll

static NSString *CellIdentifier = @"Cell";

Error log:

2015-06-28 15:46:19.379  *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(0x186d3c2d8 0x1985680e4 0x186c234c0 0x10017e7d8 0x18b7b1404 0x18b79a4e0 0x18b7b0da0 0x18b7b0a2c 0x18b7a9f68 0x18b77d18c 0x18ba1e324 0x18b77b6a0 0x186cf4240 0x186cf34e4 0x186cf1594 0x186c1d2d4 0x1904336fc 0x18b7e2fac 0x1001646d4 0x198be6a08)
libc++abi.dylib: terminating with uncaught exception of type NSException

EDIT So after trying what the others have told me to do I have done the following:

Set an exception breakpoint. The line that populates after crash is the [stringsOfObjects addObject:cell.textLabel.text]; line.

i have made sure my cellForRowAtIndexPath method was set up properly now:

}

static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

cell.backgroundColor = [UIColor whiteColor];
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
[self.tableView setRowHeight:45];
cell.textLabel.text = [self.alertSubArray objectAtIndex:indexPath.row];
cell.textLabel.textAlignment = NSTextAlignmentLeft;

return cell;

}

After that I still get a crash at adding the objects to the NSMutableArray so I looked in the debugger and made sure my UITableViewCell isn't nil like Robot mentioned to me, and it looks like it is but I don't know where to go from here: because in my mind it is not nil

image1

As you can see, I have selected 6 rows but it only added 2 objects. I don't know why this is so difficult, why is it nil when some aren't? And why can I delete them perfectly fine without scrolling to select more?


Solution

  • So, after an extensive discussion in the comments the problem seems to be the following:

    The logic of [stringsOfObjects addObject:cell.textLabel.text]; in the deleteObjects: method is wrong. This is because it is taking the text direct from the cells rather than the array backing store that populates the cells.

    Cells can be scrolled offscreen and re-used so the text in them is no longer correct and, in fact, the cell no longer "exists" as it has been reused. If the cell doesn't "exist" an empty cell will be created where the text field might be nil. Note that cell re-use is a good thing; don't create cells and never re-use them or you will run out of memory fast.

    Instead, take the text from your backing store that populates the cells themselves rather than from the cell directly. I would expect code something like:

    [stringsOfObjects addObject:[self.alertSubArray objectAtIndex:selectionIndex.row]];