UITableView崩溃后(显然)未能请求代表numberOfRowsInSection

问题描述:

numberOfRowsInSection返回的行数与数据源不一致时,这里有很多关于UITableViewController s崩溃的问题。UITableView崩溃后(显然)未能请求代表numberOfRowsInSection

我的应用程序崩溃,因为这个问题 - 但我不明白为什么。为了增加混淆,它在真实的iPhone和iPhone模拟器上崩溃,但在(真正的)iPad上完美运行。

这是错误:

2013-04-05 14:38:15.644 JobTime[28484:c07] *** Terminating app due to uncaught 
exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: 
index 3 beyond bounds [0 .. 2]' 

这是正确的 - 唯一的NSArray延伸到2

什么奇怪的是,TableView中似乎并没有要问我的控制器(被设置作为它的委托)在它自己刷新之前的部分中的行数。这是我的方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
    NSLog(@"called numberOfRowsInSection"); 
    return [[[JobStore sharedStore] allJobs] count]; 
} 

下面是在iPhone模拟器(但不是iPad的)崩溃的序列:

  1. 的TableView正常工作,一切都按预期执行,NSLog的消息以上定期弹出。
  2. 点击“添加新项目”按钮。
  3. 将新项目添加到JobStore(包含作业的NSArray的单例)。
  4. 打开一个模态导航控制器(UIModalPresentationFormSheet)来编辑该新项目。
  5. 取消编辑 - 并从JobStore中删除不需要的项目。

然后坠毁:

  1. 的TableView中更新本身,而无需调用numberOfRowsInSection(即,消息的NSLog以上没有出现),所以它试图从作为边界之外的作业存储检索对象的阵列。

我已经添加了的NSLog到对的视图控制器完成后执行被驳回dismissBlock:

[jobDetailViewController setDismissBlock:^{ 
    NSLog(@"dismissBlock"); 
    [[self tableView] reloadData]; 
    [self clearTableViewBackgroundColorAndResetHighlight]; 
}]; 

当我选择“完成”并保存模态视图的项目这是执行,但不是当我选择'取消'时。代码行是在这两种方法是相同的,我知道,正确的方法正在呼吁每个按键:如果我简化dismissBlock

[[self presentingViewController] dismissViewControllerAnimated:YES completion:dismissBlock]; 

同样如此,只是一个的NSLog声明,任何其他通话:它不会在'取消'情况下被调用。

我发现的唯一的解决办法是相当不雅一个重新检查数组的边界中的cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"]; 

    if (!cell) { 
     cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 
            reuseIdentifier:@"UITableViewCell"]; 
    } 

    // Check array bounds before trying to retrieve object from array 

    if ([indexPath row] < [self tableView:tableView numberOfRowsInSection:0]) { 
     Job *j = [[[JobStore sharedStore] allJobs] objectAtIndex:[indexPath row]]; 
     [[cell textLabel] setText:[j displayDescription]]; 
     [[cell detailTextLabel] setText:[j timeDescription]]; 

     if ([j selected]) { 
      [cell setBackgroundColor:[UIColor lightGrayColor]]; 
     } 
    } else { 
     [[cell textLabel] setText:@""]; 
     NSLog(@"gone past array bounds"); 
    } 
    return cell; 
} 

这样的作品,但它的笨拙。任何想法为什么TableView在刷新自身之前似乎没有要求更新的行数?或者为什么它可以在iPad上运行,但不是iPhone?

我开始怀疑一些不同的模态视图的怪癖,但会感激任何帮助。提前致谢。

詹姆斯

UITableView缓存值,以便它可以更有效的(这可能是每一个-depending你如何做你的计算时间问你潜在的慢)。

所以,如果你改变你的基础数据源,你需要让tableView知道它。你可以做一个完整的[self.tableView reloadData];或使用下列方法

– insertRowsAtIndexPaths:withRowAnimation:
– deleteRowsAtIndexPaths:withRowAnimation:
– moveRowAtIndexPath:toIndexPath:
– insertSections:withRowAnimation:
– deleteSections:withRowAnimation:
– moveSection:toSection:

这些通常内部调用用于

– beginUpdates
– endUpdates

检查docs

+0

感谢您的快速答复。我使用了其中几种方法来处理in-tableView编辑(移动,删除) - 但是这个问题的编辑发生在单独的UIView中。那个UIView应该在完成时调用上面提到的'dismissBlock',并且应该调用'[[self tableView] reloadData]'让tableView知道更改......但是它似乎忽略了这个代码块案件。 – James 2013-04-05 14:32:49

+0

你不能把这个逻辑放在'viewWillAppear:'而不是回调中吗?或者,您确定'done'和'cancel'遵循相同的代码路径吗? – 2013-04-05 14:36:41

+0

好的 - 感谢您的答案提供的灵感解决问题!它需要我在将新对象添加到形成数据源的数组之前调用'[[self tableView] beginUpdates]',然后我必须将'[[self tableView] endUpdates]'添加到神秘的未调用代码块调用'[[self tableView] reloadData]'。突然它全部运行,并且代码块被执行编辑的UIVIew中的两条路由调用。我学习的书(Big Nerd Ranch iOS)没有提及'beginUpdates'或'endUpdates',所以我没有使用它们。现在我明白了。再次感谢。 – James 2013-04-05 14:40:57