Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing static and dynamic UITableViewController content causes NSRangeException

I have been searching all over for this error and did find some few posts with a similar behavior but no solution that solves the problem.

I have a UITableViewController (declared as Static in SB) which has to Sections: Section 0 (Recipe) is static with 4 cells, Section 1 (Flavours) should be dynamic

Storyboard Screenshot

This is the code I'm using to test:

- (void)viewDidLoad {
    [super viewDidLoad];

    rows = [[NSMutableArray alloc] initWithArray:@[@"test",@"test",@"test",@"test",@"test",@"test",@"test"]];
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
    switch (section) {
        case 0:
        return 4;
        break;
        case 1:
        return rows.count;
        break;

        default:
        break;
    }

    return 1;
}

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

    UITableViewCell *cell;

    switch (indexPath.section) {
        case 0:
        return [super tableView:tableView cellForRowAtIndexPath:indexPath];
        break;

        case 1:

        cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
        if (!cell)
        {
// create a cell
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
        }
        cell.textLabel.text = [rows objectAtIndex:indexPath.row];
        return cell;

        default:
        break;
    }

    return cell;
}

Now the error I'm getting when running is: 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

I know I miss something small somewhere, can someone help me?

thanks so much

like image 659
DonBaron Avatar asked Dec 03 '14 13:12

DonBaron


2 Answers

The actual problem is the way how dynamic and static tableViewControllers work.

If you create a dynamic UITableViewController, most methods return nil or 0 or another default value. So nothing bad happens if you don't overwrite them in your UITableViewController subclass.

If you create a static UITableViewController, most methods "ask" the storyboard what to return. In reality there is probably something like a private array as a backing store that contains all the necessary data. And if you don't overwrite methods that query this backing store the default implementation will ask the array for objects at indexes that don't exist.


Your problem is that you tell the tableView that it has 2 sections and a couple of rows. So the tableView asks the static UITableViewController about things like the height of the cell in second row in second section. But this indexPath does not exist in the backing store (because you only put a single row into the second section) that is used by the static tableView.

So you have to implement a couple of dataSource and delegate methods and return your own values if the tableView wants to access information that does not exist in the static table store.

If you look at the call stack of the exception you should be able to see these methods.

E.g.:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack:
(
    0   CoreFoundation    0x000000010ebaaf35 __exceptionPreprocess + 165
    1   libobjc.A.dylib   0x000000010e843bb7 objc_exception_throw + 45
    2   CoreFoundation    0x000000010eaa301e -[__NSArrayI objectAtIndex:] + 190
    3   UIKit             0x000000010f4dc856 -[UITableViewDataSource tableView:heightForRowAtIndexPath:] + 109
    4   UIKit             0x000000010f21026b __66-[UISectionRowData refreshWithSection:tableView:tableViewRowData:]_block_invoke + 302
    5   UIKit             0x000000010f20f8fe -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 4125
    6   UIKit             0x000000010f214e45 -[UITableViewRowData rectForFooterInSection:heightCanBeGuessed:] + 320
    7   UIKit             0x000000010f214f3a -[UITableViewRowData heightForTable] + 56

At index 3 of the call stack you can see that -[UITableViewDataSource tableView:heightForRowAtIndexPath:] has called the objectAtIndex: code that raised the exception. This is one of the methods that relay their calls to the backend store if you don't stop them from doing that. So you have to implement this method and return something usefull. And then you continue until there are no more exceptions.

How many methods are required might depend on the configuration of your tableView. I implemented the four that usually cause these exceptions, so you can see the pattern you should follow if you see more exceptions that are caused by not overwritten UITableViewDataSource/Delegate methods:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    CGFloat height = 0;
    if (section == 0) {
        // static section
        height = [super tableView:tableView heightForHeaderInSection:section];
    }
    return height;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    CGFloat height = 0;
    if (section == 0) {
        // static section
        height = [super tableView:tableView heightForFooterInSection:section];
    }
    return height;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    CGFloat height = 44;
    if (indexPath.section == 0) {
        // static section
        height = [super tableView:tableView heightForRowAtIndexPath:indexPath];
    }
    return height;
}

- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSInteger indentationLevel = 0;
    if (indexPath.section == 0) {
        // static section
        indentationLevel = [super tableView:tableView indentationLevelForRowAtIndexPath:indexPath];
    }
    return indentationLevel;
}

and here is a little trick to make your static content even more independent from your code:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0) {
        // static section
        return [super tableView:tableView numberOfRowsInSection:section];
    }
    return self.objects.count;
}

If you mix dynamic and static cells, calling super becomes very useful.

like image 106
Matthias Bauch Avatar answered Oct 04 '22 04:10

Matthias Bauch


It seems that this behavior is not trivial. You may need to override all methods with NSIndexPath. You can read this discussion and this one for more information.

like image 32
Y.Bonafons Avatar answered Oct 04 '22 05:10

Y.Bonafons