-1

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?

luk2302
  • 55,258
  • 23
  • 97
  • 137
Jteve Sobs
  • 244
  • 1
  • 14
  • When you call dequeueReusableCellWithIdentifier, you definitely get a reused cell (after it has created the default 9 or so cells. If you want to not reuse cells, just init a cell rather than calling the dequeue method. – Woodstock Jun 28 '15 at 19:00
  • its not that I don't want to reuse them, i'm sure it's more memory efficient if i do, it's just that that's the only way I can addObjects to my array without it being nil (that I know how to do anyways). i tried following this tutorial : https://developer.apple.com/library/ios/samplecode/TableMultiSelect/Introduction/Intro.html and used the removeObjectAtIndexes with the same result (it ended up deleting objects I didn't want to be deleted becasue the indexes changed after scrolling) @Woodstock – Jteve Sobs Jun 28 '15 at 19:04
  • where does it crash? in `[stringsOfObjects addObject:cell.textLabel.text];`? – luk2302 Jun 28 '15 at 19:20
  • @luk2302 it crashes as soon as I press the delete button and the error that shows up is the one stated above. "NSArryM insert object" can't be nil. so that makes me think that it's because the cells are being reused but I'm positive that's where it crashes cause that's the only code in the method trying to add something and it works 100% unless I scroll – Jteve Sobs Jun 28 '15 at 19:30
  • it normally breaks the program at some line and shows you that line, therefore showing the statement actually causing the crash. Alternatively [add an `Exception Breakpoint`](https://developer.apple.com/library/ios/recipes/xcode_help-breakpoint_navigator/articles/adding_an_exception_breakpoint.html) – luk2302 Jun 28 '15 at 19:33
  • `1)` Set an Objective-C breakpoint (https://developer.apple.com/library/ios/recipes/xcode_help-breakpoint_navigator/articles/adding_an_exception_breakpoint.html) so you know which line the problem occurs on. `2)` Use the debugger to step through your code and check the object arguments. This should be the first step to fixing code NOT StackOverflow. Post the full method and inform us of the problem line. `3)` Always use `dequeueReusableCellWithIdentifier:forIndexPath:` as it is incredibly efficient; don't keep creating new cells – Robotic Cat Jun 28 '15 at 19:44
  • I get a SIGABRT crash but i updated my question with the full crash log...it's literally all that it states, there is no lines annotated...but i did add a breakpoint with the link you provided @luk2302 and it highlights the `[stringOfObjects addObject:]` line and says Thread 1: breakpoint 1.1 and same thing `[NSArrayM insertObject:atIndex:]` can't be nil – Jteve Sobs Jun 28 '15 at 19:45
  • @RoboticCat thanks. I did it as luk suggested. see comment above. the full code doesn't matter i don't think but will update here within seconds for reference if it helps i'll share :) – Jteve Sobs Jun 28 '15 at 19:48
  • You are adding `nil` objects to an array which is a bug and not allowed. The solution to your problem is to NOT add `nil` objects. Basically you have a logic problem in your code and you need to fix it. Either check for `nil` objects and not add them or use `[NSNull null]` – Robotic Cat Jun 28 '15 at 19:50
  • That looks like `cell.textLabel.text` is nil, no idea why that may be because you actually set the text in the `cellForRowAtIndexPath`...! – luk2302 Jun 28 '15 at 19:51
  • @JteveSobs: Don't bother with adding the full method; you have a lot of logic problems with your code around `nil` objects and you are not using `dequeueReusableCellWithIdentifier:forIndexPath:`. I recommend you restructure your code to correctly use `dequeueReusableCellWithIdentifier:forIndexPath:` and alter your logic to deal with `nil` objects. – Robotic Cat Jun 28 '15 at 19:52
  • @RoboticCat all the cells have a textLabel filled out and i retrieve them from a plist. There aren't nil objects, because it works, i can delete the objects when they are in view, they will animate out and delete from the table, then i can delete the next ones, and repeat til all of them are gone. But as soon as I scroll it crashes – Jteve Sobs Jun 28 '15 at 19:53
  • @RoboticCat i just don't understand when i'm following apples guide to the T, with the exception of not using an `NSIndexSet` to delete multiple indexes – Jteve Sobs Jun 28 '15 at 19:56
  • Yeah me either @luk2302 thanks for you help though. At least i learned something out of it. Learned how to set a exception breakpoint so thats cool – Jteve Sobs Jun 28 '15 at 19:57
  • have you tried setting a normal breakpoint and stepping through the code line by line and check if the selectedRows actually contains the valid index paths? you need to narrow down the actual cause of the error. – luk2302 Jun 28 '15 at 20:00
  • Use the debugger; you have a logic problem and you need to find out where it is because you are adding `nil` objects to an array which is a bug. – Robotic Cat Jun 28 '15 at 20:00
  • @RoboticCat what's the correct structure for dequeueResuableCellWithIdentifier if you don't mind? – Jteve Sobs Jun 28 '15 at 20:00
  • @luk2302 selectRows contains valid indexPaths, even if i scroll. I think the issue is that i'm adding the strings of the cells label to the array as opposed to objectAtIndex per sey, but even when I tried the NSIndexSet it resulted with the same conclusion when i scrolled – Jteve Sobs Jun 28 '15 at 20:06
  • @JteveSobs: I can't give you code because I don't know your app but basically `1)` Design your cell in Storyboard and give it an identifier `2)` Use the identifier in `dequeueReusableCellWithIdentifier:forIndexPath:` *not* `nil` as you currently have `3)` Remove your `if (cell == nil) { ... }` code and keep the `tableView:cellForRowAtIndexPath:` method as simple (and quick) as possible. `4)` If necessary use a custom `UITableViewCell` subclass for your Storyboard cell (depends on app if this is required). `5)` Fix your logic errors by using the debugger to step through your code – Robotic Cat Jun 28 '15 at 20:10
  • Okay thanks @RoboticCat I've tried/currently done everything you stated except deleting the if cell == nil part. I'll give her a whirl and let you know tonight. Thanks for your patience – Jteve Sobs Jun 28 '15 at 20:16
  • @JteveSobs: As an aside, based on your previous comments, `cell.textLabel.text` must be `nil` inside your `deleteObjects:` method if that is the line the app crashes on. This needs to be fixed. Check the `cell` object on the previous line in the debugger (set a breakpoint on the `UITableViewCell *cell = ...` line and make sure the `textLabel.text` is not `nil`. If this is not the line your code crashes on you need to find that line. – Robotic Cat Jun 28 '15 at 20:21
  • @RoboticCat I have updated my question for review...it turns out some of my cells are nil in the for loop – Jteve Sobs Jun 28 '15 at 22:13
  • @luk2302 can you take another gander at the question please. more eyes the better – Jteve Sobs Jun 28 '15 at 22:13
  • 1
    @JteveSobs: There's no image. Also, I've reached the limit of what I can do for you. One final comment: the logic of `[stringsOfObjects addObject:cell.textLabel.text];` is outright wrong because cells can be scrolled offscreen and re-used so the text is no longer correct. Rather, you should be taking the text from your backing store that populates the cells themselves. I would expect code something like: `[stringsOfObjects addObject:[self.alertSubArray objectAtIndex:selectionIndex.row]];` – Robotic Cat Jun 28 '15 at 22:56
  • @JteveSobs: This could, in fact, be the source of your problem because when you create the cell in `deleteObjects:`, if it doesn't already exist an empty cell will be created where the text field might be `nil` – Robotic Cat Jun 28 '15 at 22:58
  • @RoboticCat yeah idk what happened to the image, but it was the debugger :/ firstly, again, thank you for your patience, naturally you already know I'm new to this whole environment, so your time to teach and explain is greatly appreciated. I learned a lot tonight already. You just fixed the issue, all I add to do was the later part of your comment, taking the text from the array itself. If only we started with that ;) If you care to answer the question, i would love to give you credit for your time I am so thankful now that I can move on with this project and learning the basics :) thank you! – Jteve Sobs Jun 28 '15 at 23:11
  • I'll add an answer. Glad to be of help. – Robotic Cat Jun 28 '15 at 23:21

1 Answers1

2

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]];

Robotic Cat
  • 5,899
  • 4
  • 41
  • 58
  • Well, it was clearly to late when I took part in the discussion yesterday, for some reason my brain thought that you can only select cells that are on the screen... :/ – luk2302 Jun 29 '15 at 06:39
  • @luk2302: Yeah - me too. Somewhat obvious in retrospect but it's very difficult to debug someone's code remotely. – Robotic Cat Jun 29 '15 at 10:21