-->

Mixing static and dynamic UITableViewController co

2019-05-01 06:40发布

问题:

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

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

回答1:

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.



回答2:

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.



回答3:

So it seems like I found the reason for this error to occur. The question is now whether I solved it correctly or whether there's another way to do programatically...

My Solution is: In the SB, select the section which should be dynamic and provide the number of maximum rows it will contain. Per se, in my case I don't want to let the user add more than 30 flavours to a recipe, so I provided 30 rows for this section.

When running the app, my Array has only 7 objects, although I set the rows to 30 for the section, it renders only the 7 defined in the array and doesn't display the other 23.

The reason for that error to occur seems to be that when you wanna mix static and dynamic sections, you gotta define your table to be static in order to work. In static mode and with SB, it seems like the code reads the number of cells you have in SB for a section. If this number is smaller than your data array, it spits an NSRangeException...

For now this is my solution as I know I won't have more than 30 cells, if someone knows how to remedy this issue in a more dynamic way, please let us know



回答4:

I replaced the call to super with UITableViewAutomaticDimension for row heights and added the delegate method for estimated row height. In iOS 9 I was having issues with rows not expanding that did expand automatically in iOS8. My static cells were also dynamic heights and dynamic cells are fixed height.

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

}

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 44;

}