混合静态和动态UITableViewController内容会导致NSRangeException

我一直在搜索这个错误,并找到了一些类似行为的post,但没有解决问题的解决方案。

我有一个UITableViewController(在SB中声明为Static),它必须包含Sections:Section 0(Recipe)是静态的4个单元格,Section 1(Flavors)应该是动态的

故事板截图

这是我用来测试的代码:

- (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; } 

现在我在运行时遇到的错误是:’NSRangeException’,原因:’*** – [__ NSArrayI objectAtIndex:]:索引1超出边界[0 .. 0]’

我知道我想念一些小东西,有人能帮帮我吗?

非常感谢

实际问题是tableViewControllers动态和静态的工作方式。

如果创建动态UITableViewController,则大多数方法返回nil或0或其他默认值。 如果你不在UITableViewController子类中覆盖它们,那么没有什么不好的事情发生。

如果您创建一个静态UITableViewController,大多数方法都会“询问”故事板要返回的内容。 实际上,可能存在类似私有数组作为后备存储的东西,其中包含所有必需的数据。 如果不覆盖查询此后备存储的方法,则默认实现将向数组请求不存在的索引处的对象。


你的问题是你告诉tableView它有2个部分和几行。 因此tableView会询问静态UITableViewController,例如第二部分第二行中单元格的高度。 但是这个indexPath不存在于静态tableView使用的后备存储中(因为您只将一行放入第二部分)。

因此,如果tableView想要访问静态表存储中不存在的信息,则必须实现几个dataSource和delegate方法并返回自己的值。

如果查看exception的调用堆栈,您应该能够看到这些方法。

例如:

 *** 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 

在调用堆栈的索引3处,您可以看到-[UITableViewDataSource tableView:heightForRowAtIndexPath:]已调用objectAtIndex:引发exception的代码。 如果您不阻止它们执行此操作,这是将其调用转发到后端存储的方法之一。 所以你必须实现这个方法并返回一些有用的东西。 然后你继续,直到没有更多例外。

需要多少方法可能取决于tableView的配置。 我实现了通常导致这些exception的四个,所以如果你看到更多exception是由未覆盖的UITableViewDataSource / Delegate方法引起的,你可以看到你应该遵循的模式:

 - (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; } 

这里有一个小技巧可以让您的静态内容更加独立于您的代码:

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

如果混合动态和静态单元格,调用super会变得非常有用。

看来这种行为并非无足轻重。 您可能需要使用NSIndexPath覆盖所有方法。 您可以阅读此讨论和此讨论以获取更多信息。

所以我好像发现了这个错误发生的原因。 现在的问题是我是否正确地解决了它或者是否有其他方式以编程方式进行…

我的解决方案是:在SB中,选择应该是动态的部分,并提供它将包含的最大行数。 本身,在我的情况下,我不想让用户为配方添加超过30种口味,所以我为此部分提供了30行。

运行应用程序时,我的数组只有7个对象,虽然我将该行的行设置为30,但它只渲染数组中定义的7个,而不显示其他23个。

出现该错误的原因似乎是,当您想要混合静态和动态部分时,您必须将您的表定义为静态才能工作。 在静态模式和SB中,似乎代码读取了SB中某个部分的单元格数。 如果此数字小于数据数组,则会吐出NSRangeException …

目前这是我的解决方案,因为我知道我不会超过30个单元格,如果有人知道如何以更动态的方式解决这个问题,请告诉我们

我将UITableViewAutomaticDimension的调用替换为行高,并为估计的行高添加了委托方法。 在iOS 9中,我遇到的问题是没有扩展的行在iOS8中自动扩展。 我的静态细胞也是动态高度,动态细胞是固定高度。

 - (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; 

}