9

This is very confusing.

I have a UITableView, which updates and works fine until it gets more than 16 items then it crashes when trying to endUpdates after calling insertRowsAtIndexPaths.

The NSIndexPaths being added are all valid. -numberOfRowsInSection returns the correct number. It is not throwing an error related to the data set, rather it crashes with

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

when endUpdates is called on the tableView.

The data source is all there, the NSIndexPaths are fine. the code works fine between 0 and 16 rows, but when I add a 17th it crashes. Additionally if I start it with 22 items it works fine, when I add the 23rd it crashes... if I call reload data instead of doing the update and insert process it works fine, so it's nothing to do with the data itself, and it shouldn't be anything to do with how I'm inserting the rows since it works through 16...

I'm completely perplexed. Here is my update method. It is being called on the main thread at all times.

- (void)updateConversation:(NSNotification*)notification
{
    NSDictionary *updateInfo = [notification userInfo];
    //NSLog(@"Got update %@", updateInfo);

    if ([[updateInfo objectForKey:@"success"] integerValue] == YES) {
        [self updateConversationUI];

        int addedStatementCount = [[updateInfo objectForKey:@"addedStatementCount"] intValue];

        if (addedStatementCount > 0) {
            //[self.tableView reloadData];
            [self.tableView beginUpdates];
            int previousStatmentCount = [[updateInfo objectForKey:@"previousStatmentCount"] intValue];

            NSLog(@"owner %i, Was %i, now %i, change of %i", self.owner, previousStatmentCount, (int)self.conversation.statements.count, addedStatementCount);

            NSMutableArray *rowPaths = [[NSMutableArray alloc] init];

            for (int i = previousStatmentCount; i < previousStatmentCount + addedStatementCount; i++) {
                NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
                [rowPaths addObject:path];
            }

            [self.tableView insertRowsAtIndexPaths:rowPaths withRowAnimation:UITableViewRowAnimationBottom];        
            [self.tableView endUpdates];
            [self.tableView scrollToRowAtIndexPath:[rowPaths lastObject] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
        }
    }
}

The rest of the crash past [self.tableView endUpdates] is UITableView

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010189b795 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x00000001015fe991 objc_exception_throw + 43
    2   CoreFoundation                      0x0000000101852564 -[__NSArrayM insertObject:atIndex:] + 820
    3   UIKit                               0x0000000100317900 __46-[UITableView _updateWithItems:updateSupport:]_block_invoke691 + 173
    4   UIKit                               0x00000001002b5daf +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 460
    5   UIKit                               0x00000001002b6004 +[UIView(UIViewAnimationWithBlocks) animateWithDuration:delay:options:animations:completion:] + 57
    6   UIKit                               0x00000001003174cb -[UITableView _updateWithItems:updateSupport:] + 2632
    7   UIKit                               0x0000000100312b18 -[UITableView _endCellAnimationsWithContext:] + 11615
    8   Dev App                         0x0000000100006036 -[ConversationViewController updateConversation:] + 998
    9   CoreFoundation                      0x00000001018f121c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
    10  CoreFoundation                      0x000000010185370d _CFXNotificationPost + 2381
    11  Dev App                         0x00000001000055ac -[ConversationManager postNotification:] + 92
    12  Foundation                          0x0000000101204557 __NSThreadPerformPerform + 227
    13  CoreFoundation                      0x000000010182aec1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    14  CoreFoundation                      0x000000010182a792 __CFRunLoopDoSources0 + 242
    15  CoreFoundation                      0x000000010184661f __CFRunLoopRun + 767
    16  CoreFoundation                      0x0000000101845f33 CFRunLoopRunSpecific + 467
    17  GraphicsServices                    0x00000001039a23a0 GSEventRunModal + 161
    18  UIKit                               0x0000000100261043 UIApplicationMain + 1010
    19  Dev App                         0x0000000100003613 main + 115
    20  libdyld.dylib                       0x0000000101f2a5fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

This seems like a bug in the OS, the stack would indicate that it's something to do with the animation block but it happens no matter what I set the animation to, including none.

And to re-state. This works through 16 items, then inserting items past that causes a crash. It is also called at initial setup to load data, and it will load any number of items there, including well over 16. But when called again, purely as an update from 16 or more to something higher it will crash.

Most bizarre issue I've yet to encounter with table views...

under iOS 7 on device and simulator, latest Xcode for reference.

ima747
  • 4,667
  • 3
  • 36
  • 46
  • Enable exception breakpoint and see where you are trying to insert nil into NSArray – yurish Dec 15 '13 at 14:46
  • Enable Zombie and see the exact crash report. I guess you are iterating through the same datasource while you are mutating it. – Saify Dec 15 '13 at 15:00
  • 1
    Try commenting out the line `[self.tableView scrollToRowAtIndexPath:[rowPaths lastObject] atScrollPosition:UITableViewScrollPositionBottom animated:YES];`. If that is indeed the problem, I will elaborate and suggest a couple of alternatives. – Timothy Moose Dec 15 '13 at 16:52
  • I have enabled exception break point, and as is indicated from the rest of the stack when it crashes, I'm not inserting nil, something deeper in the OS is (looks like in the animation handler) – ima747 Dec 15 '13 at 17:51
  • What is the call stack at exception if you disable all animations? – yurish Dec 15 '13 at 18:30
  • Using withRowAnimation:UITableViewRowAnimationNone results in the same crash. Updating post with cleaner call stack dump – ima747 Dec 16 '13 at 13:57
  • @TimothyMoose I was wrong, even though the crash occurs before the scroll commenting out the scroll does indeed prevent it from crashing... please advise on some alternatives, everything I've tried hasn't worked (delaying, threading, accessing scroll directly, etc.) – ima747 Dec 18 '13 at 14:08
  • This problem still persists in 7.1. Radar here: http://openradar.io/15729686 – axelarge Jun 04 '14 at 11:04

4 Answers4

20

It seems that the problem is caused by my call of scrollToRowAtIndexPath (even though it crashes before it gets there...) combined with implementing tableView:estimatedHeightForRowAtIndexPath: by removing the row height estimate the crash went away... still seems like a bug in the animation system for tables to me. Thankfully I don't need the estimated row height, I had forgotten I had implemented it (trying to play nice by iOS 7 bites me again).

ima747
  • 4,667
  • 3
  • 36
  • 46
  • Just got bitten by this as well. Spent a few hours stepping through things, and finally came across your answer. Thanks for saving me the rest of the day ! – Bart Vandendriessche Feb 20 '14 at 10:31
  • Setting the estimatedSectionHeaderHeight property on a UITableView seems to cause the same issue on iOS 8.2. Thanks! – John M. P. Knox Nov 18 '15 at 20:22
  • The problem still exists on iOS 9.3.1 Does apple fix bugs? – Voloda2 Jun 02 '16 at 08:32
  • I've made powerful forms based on UITableView which supports dynamic sections and cells manipulation (visibility change due to changes in model and editing state). Unfortunately due to this bug I had to add cell count check and above 15 cells I just call reloadData with UIView transition to still have some animations. – thom_ek Jun 28 '16 at 13:35
0

Place strategic breakpoints when you are incrementing the statement count, go through the loop of adding the rows as many times as you need to, and locate the statement that it's causing the crash, at that moment take a look at your objects and look for nil values as the error you are having it's trying to insert a nil object(or un-existent) from an Array.

I would personally recommend you to go through this loop entirely while keeping an exe for the path and previous statement count .

            NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
Joel Hernandez
  • 1,817
  • 4
  • 18
  • 27
  • I have logged all the path inserts, and then also logged the array at the end, additionally I check the value of the array when it crashes and there are no nil values anywhere. Per the rest of the stack it looks like the issue is deeper in the OS with the animation system trying to insert a nil value. – ima747 Dec 15 '13 at 17:53
0

in my case the tableView:heightForHeaderInSection: method has been missing, hadn't implemented the tableView:estimatedHeightForRowAtIndexPath method so this also could be solving the problem if you aren't using tableView:estimatedHeightForRowAtIndexPath:

-2

Your problem is that you don't update the dataSource the tableView is using. if you insert a row, you need to insert the appropriate object to the array the tableView is using.

Goodluck!

Lirik
  • 3,167
  • 1
  • 30
  • 31
  • 1
    @ima747 mentioned `reloadData` works fine (_hence his datasource **is** being updated_) – staticVoidMan Dec 15 '13 at 15:52
  • The data source is updated. Verified by dumping it, counting it, using reload rows, and the fact that the same code works up to 16 entries (regardless of content). – ima747 Dec 15 '13 at 17:49
  • To further clarify the data source is updated prior to the UpdateConversation method being called. When the data source updates it sends out a notification (caught by this method) telling any tables that display that data source to update. Further if I didn't update the data source, or was inserting the incorrect number of rows I would be getting a different fatal error: specifically that the data source must match the table size after when calling endUpdates. – ima747 Dec 15 '13 at 17:55