14

I am trying to insert an item to my table view dynamically. My app has a chat section, where it displays the old (loaded from server before initializing the view controller) messages in section 0, and displays just sent/received messages (where it is initially zero) in section 1. When the view is loaded, all the "old" messages are loaded and displayed, no problem is there. The problem starts when I try to insert rows. Here is what I am doing:

  • I am first updating my table view's data source by adding an extra item: [newMessages addObject:newMessage]; (newMessage is an instance of my custom message object, and newMessages is my data source). I verify that my data source now has 1 item (which was 0 before adding).
  • I then call the following code:

    [self.chatTableView beginUpdates];
    [self.chatTableView insertRowsAtIndexPaths:@[[NSIndexPath 
        indexPathForRow:newMessages.count - 1 inSection:1]] 
        withRowAnimation:UITableViewRowAnimationBottom];
    [self.chatTableView endUpdates];
    

My app crashes at endUpdates method, giving me this error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

I've immediately checked if newMessage is nil or not, it's not nil (and re-checked my data source) so data source is not the problem. I've thought that indexPathForRow:newMessages.count - 1 could be the problem and tried different values (count - 2, count, count + 1) just in case I was missing something. In those cases, I'm getting another error: 'NSInternalInconsistencyException', reason: 'attempt to insert row 1 into section 1, but there are only 1 rows in section 1 after the update'. The error says it all, so the problem is not something with indexPathForRow:newMessages.count - 1 either.

I've added breakpoints into the -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method to see when it's exactly called and what it returns. It seems that the method is not called at all, the breakpoint is not hitting (it DOES hit when view is first loaded and loads the initial data correctly, and I'm not setting data source anywhere, so delegates/data sources are also connected correctly). Immediately I've checked other methods:

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 2;
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    if(section == 0){
        return @"Eski mesajlar";
    }else{
        return @"Yeni mesajlar";
    }
}

Those methods return correct values. I've put a breakpoint in -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView to see when it's called. It apparently is called inside the [self.chatTableView endUpdates]; method (as seen from the thread/queue's call stack), but then [self.chatTableView endUpdates]; immediately throws the error without even entering -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method. I've seen examples (and a few others) such as:

I've read the answers there, but none of them helped me. What am I doing wrong?

Community
  • 1
  • 1
Can Poyrazoğlu
  • 33,241
  • 48
  • 191
  • 389

5 Answers5

30

Do you have tableView:estimatedHeightForRowAtIndexPath: implemented? I've noticed a similar issue inside one of my own applications that used an NSFetchedResultsController where the application would crash with the same error message when tableView:endUpdates was called. Commenting out tableView:estimatedHeightForRowAtIndexPath: fixed it for me.

Kamaros
  • 4,536
  • 1
  • 24
  • 39
  • Exactly. I had that method, and commenting out it fixed the issue. Thanks, I probably would never have figured it out, I'll be filing a bug report to Apple. – Can Poyrazoğlu Mar 06 '14 at 09:14
  • I'm not using a fetched results controller, but basing mine off of networked data and I can confirm that commenting out the estimatedHeight section stopped my crash as well. – adamweeks Mar 26 '14 at 16:27
  • This answer saved my life! Resolved a crash AND made the insertion animations FAR smoother! Thanks! – Alfie Hanssen Apr 23 '14 at 18:45
  • 2
    When you remove the `tableView:estimatedHeightForRowAtIndexPath:` method (or stop using the `estimatedRowHeight` property), just make sure you fully understand what you're doing. You're now forcing the table view to call `tableView:heightForRowAtIndexPath:` for **every** row in the table view before anything can be shown *and* at every call to `reloadData` or similar -- major performance impact! As such, removing the usage of row height estimation is not really a fix, just a workaround for what appears to be an Apple bug. (@CanPoyrazoğlu do you have the radar # for the bug you filed?) – smileyborg Apr 23 '14 at 20:18
  • The bug ID is 16246389 but as it's fixed in iOS 7.1, bug is in closed state now. – Can Poyrazoğlu Apr 24 '14 at 07:40
  • @CanPoyrazoğlu Interesting! Alfie Hanssen was saying he ran into this, sounds like it was on an older version of iOS or a different issue altogether. – smileyborg Apr 25 '14 at 00:40
  • @smileyborg The bug is apparent in iOS 7.0. It has been corrected on 7.1, and I don't know about the versions prior to 7.0. He is probably on iOS 7.0. – Can Poyrazoğlu Apr 25 '14 at 09:31
  • 3
    I don't think this "bug" is completely resolved because I'm having the same crash/Exception issue and commenting out tableView:estimatedHeightForRowAtIndexPath: makes it go away (but makes tableView scrolling jerky and unusable.) – race_carr May 19 '14 at 16:26
  • Removing any `tableView:estimatedHeightForRowAtIndexPath:` calls is not the way to go about this. This is a very important delegate method. I'd say to try out @speby's answer below before you go removing important delegate methods/properties. – Sakiboy Oct 28 '15 at 03:05
  • Crash is still happening for dynamic header height. This saved my week :) – Dileep Jul 14 '16 at 20:21
11

Regarding removing/commenting out tableView:estimatedHeightForRowAtIndexPath:, that was not how I resolved it since I did not have that method on my delegate.

Instead, I had actually set tableView.estimatedSectionHeaderHeight = 10.; in my viewDidLoad. Turns out by simply commenting that line out, the exception no longer occurs and this then works as I expect it to.

It's too bad, though, because I can't say why commenting this out (or the estimatedHeightForRowAtIndexPath: method) fixes it.

speby
  • 360
  • 4
  • 7
  • This helped me, removing any `tableView.estimatedSectionHeaderHeight` and `tableView.sectionHeaderHeight` calls fixed this up for me. This isn't the first time they've caused me trouble...I'm just going to pretend they don't exist from now on, because they only break things... – Sakiboy Oct 28 '15 at 03:02
  • This fixed the problem for me too, on iOS 9.2 (!) – bcattle Feb 29 '16 at 21:55
  • Dear Lord, this is what helped me too! Thank you for existing @speby ! I _had_ to keep the `rowHeight = UITableViewAutomaticDimension` tho, otherwise it would still fail. – Gerald Eersteling Oct 24 '16 at 19:16
  • You're welcome @Gee.E! Glad I could help. This was a real time sink for awhile! – speby Oct 27 '16 at 21:04
3

I recently resolved this issue without commenting out estimatedHeightForRowAtIndexPath:. The specific issue I had was that our table had many cells with different sizes and so we were simply using the average size in the estimation. The fix was to better estimate the size of the inserted cells.

My assumption is that the bug is caused by the table attempting to scroll to the inserted cell. Apparently the previously estimated contentSize causes issues when scrolling to inserted cells with heights that were estimated too small.

Sean G
  • 479
  • 4
  • 9
1

I had this problem and fixed it by not inserting if the table is empty. In other words if the table has no rows then don't use insertRowAtIndexPaths. Instead add the object to your array or whatever data source and then call [myTableView reloadData]

smDeveloper
  • 1,068
  • 9
  • 12
-2

I believe your error is in the tableView:numberOfRowsInSection: method. The insertRowsAtIndexPaths method will call this method before calling tableView:cellForRowAtIndexPath and it this doesn't match to the data source, it could be the problem.

I see that newMessages is a local variable, so that way it is probably not synchronised to the array that returns from tableView:numberOfRowsInSection: method.

To fix the problem, just make sure the tableView:numberOfRowsInSection returns the correct number when doing the updates.

In addition: even if newMessages was nil, the count method would return 0 - Objective-C works that way.

Legoless
  • 10,942
  • 7
  • 48
  • 68