user955674
user955674

Reputation:

Strange error: insertRowsAtIndexPaths in UITableView crashes with NSInternalInconsistencyException

I have searched for a more fitting answer to NSInternalInconsistencyException I receive in the following sample app I wrote, but still nothing. The goal is to create an expand/collapse functionality for the top row in each section of the tableView. Right now I try to implement the expand part, and this works for row 0 in section 0. As soon as the user taps row 0 in another section this error appears:

** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unable to resolve row for index path:  2 indexes [0, 1]'

This is strange since I store each and every UITableViewCell for the table in a mutable array of arrays. NSMutableArray *cellForRow, where each index represents a section in the table and each object is an object of type NSMutableArray. I do this to avoid any issues arising from queueing reusable cells that I first thought triggered the above exception.

The exception happens at the insertRowsAtIndexPaths statement. I read earlier here that the UITableViewController code must keep track of changes to the number of rows caused by insertions/deletion. I believe I do that with NSMutableArray *rowsInSection so that the UITableView data source method:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

returns the correct number of rows in a section after a change.

What am I doing wrong in my code to get the above mentioned exception?


This is the interface file:

#import <UIKit/UIKit.h> 
#import <QuartzCore/QuartzCore.h>

@interface MasterViewController : UITableViewController {
  NSMutableArray *rowsInSection;
  NSMutableArray *cellForRow;
}

@property (nonatomic,strong) NSMutableArray *rowsInSection;
@property (nonatomic,strong) NSMutableArray *cellForRow;

@end

And this is the implementation file:

#import "MasterViewController.h"

const NSInteger numSections = 4;
const NSInteger numRows = 1 + 4;
const NSInteger addRemoveRows = 4;

@implementation MasterViewController

@synthesize rowsInSection;
@synthesize cellForRow;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) {

        self.title = @"Table View";
        rowsInSection = [NSMutableArray arrayWithCapacity:numSections];
        cellForRow = [NSMutableArray arrayWithCapacity:numSections];
    }
    return self;
}


#pragma mark - View lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    self.tableView.backgroundColor = [UIColor clearColor];
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    self.tableView.dataSource = self;
    self.tableView.delegate = self;

    // add number of rows for section
    for (NSInteger i = 0; i < numSections; i++) {
        [self.rowsInSection addObject:[NSNumber numberWithInteger:1]];
    }

    // container for reusable table view cells
    for (NSInteger i = 0; i < numSections; i++) {

        NSMutableArray *rowsArray = [NSMutableArray arrayWithCapacity:numRows];

        for (NSInteger j = 0; j < numRows; j++) {

            // top row in section
            if (j == 0) {
                UITableViewCell *topCell = [[UITableViewCell alloc] 
                                            initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
                topCell.accessoryType = UITableViewCellAccessoryNone;
                topCell.contentView.backgroundColor = [UIColor whiteColor];
                topCell.textLabel.textColor = [UIColor blueColor];
                [rowsArray addObject:topCell];

                // the rest
            } else {
                UITableViewCell *simpleCell = [[UITableViewCell alloc] 
                                               initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
                simpleCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
                simpleCell.textLabel.textColor = [UIColor whiteColor];
                [rowsArray addObject:simpleCell];
            }
        }

        // add rows for current section into cell container
        [self.cellForRow addObject:rowsArray];
    }

}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return numSections;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    NSInteger rows = [(NSNumber *)[self.rowsInSection objectAtIndex:section] integerValue];

    //NSLog(@"%@",self.rowsInSection);
    //NSLog(@"Rows: %d in section: %d",rows,section);

    return rows;
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    // Configure the cell.

    // row count
    NSLog(@"Rows: %d in section: %d",[tableView numberOfRowsInSection:indexPath.section],indexPath.section);


    if (indexPath.row == 0) {
        UITableViewCell *cell = (UITableViewCell *)[[self.cellForRow objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
        cell.textLabel.text = @"TOP ROW";
        NSLog(@"Row: %d in section: %d - %@",indexPath.row,indexPath.section,cell);
        return cell;
    } else {
        UITableViewCell *cell = (UITableViewCell *)[[self.cellForRow objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
        cell.textLabel.text = [NSString stringWithFormat:@"row: %d",indexPath.row];
        NSLog(@"Row: %d in section: %d - %@",indexPath.row,indexPath.section,cell);
        return cell;
    }

    // not reaching here
    return nil;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [NSString stringWithFormat:@"Section %d",section];
}


#pragma mark - Row editing

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    // add table view cells to section if tapped on top row
    if (indexPath.row == 0 && [tableView numberOfRowsInSection:indexPath.section] == 1) {

        //NSLog(@"Selected row: %d in section: %d",indexPath.row,indexPath.section);

        NSMutableArray *indexPathArray = [NSMutableArray array];

        for (NSInteger i = 1; i <= addRemoveRows; i++) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:indexPath.section];
            [indexPathArray addObject:indexPath];
        }

        // update row count for section
        NSInteger newRowCount = addRemoveRows + 1; // +1 for existing top row
        [self.rowsInSection replaceObjectAtIndex:indexPath.section withObject:[NSNumber numberWithInteger:newRowCount]];


        [tableView beginUpdates];
        [tableView insertRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationTop];
        [tableView endUpdates];

    }
}

@end

Upvotes: 2

Views: 6187

Answers (4)

parker
parker

Reputation: 1

In this method

-(void)tableView:(UITableView)tableView deleteRowsAtIndexPaths:(NSArray)indexPath withRowAnimation:(UITableViewRowAnimation)animation;

your variable NSIndexPath *indexPath is repetition with the variable of this method

Upvotes: 0

migs647
migs647

Reputation: 847

I was having this issue as well.

It wasn't something I was doing wrong inside the body of code, but I didn't realize an object was being inserted into my dataSource at another time. If you're getting this issue, make sure you follow bshirley's advice and stick a breakpoint on numberOfRowsInSection AND the body of code where you add the item. Make sure the number is the same amount of items in your dataSource throughout the lifecycle of adding the data. There really isn't any reason it should fail.

It is fairly simple, just make sure you're keeping your data and indexpath amounts before and after the update. Otherwise this exception will be thrown.

Upvotes: 1

bshirley
bshirley

Reputation: 8357

If you are inserting/deleting multiple rows at the same time it has to be bracketed with calls to beginUpdates/endUpdates.

The table view data source needs to be consistent with your insert/delete calls. Set a break point in numberOfRowsInSection to assure that the number of rows in a section is correct after the insert/delete rows.

(i'm not going to read through all your code to debug it for you.)

good luck!

Upvotes: 5

Ariel
Ariel

Reputation: 2440

You are not only inserting cells, you also deleting old row, but the table view does not know about it as you haven't told it. So the table view "knows" it has one row, then you tell it you've added lets say two rows. The table view knows, that it should contain 3 rows BUT it founds only two as you've deleted the old one... What you need is or to use -(void)tableView:(UITableView)tableView deleteRowsAtIndexPaths:(NSArray)indexPath withRowAnimation:(UITableViewRowAnimation)animation; or delete one index path from array of added rows...
And by the way, return to use reusable cells as it have no connection with the error you've faced...

Upvotes: 0

Related Questions