UITableView在(显然)没有向delegate请求numberOfRowsInSection后崩溃

时间:2013-04-05 14:18:00

标签: ios objective-c crash uitableview

UITableViewController返回的行数与数据源不一致时,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. 打开一个模态navigationController(UIModalPresentationFormSheet)来编辑该新项目。
  5. 取消编辑 - 并从JobStore中删除不需要的项目。
  6. 然后崩溃:

    1. TableView在不调用numberOfRowsInSection的情况下自行更新(即上面的NSLog消息没有出现),因此它尝试从JobStore中检索一个超出数组范围的对象。
    2. 我已经在解除ViewController完成时执行的dismissBlock添加了一个NSLog:

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

      当我选择'完成'并从模态视图中保存项目时执行该操作,但当我选择'取消'时。两种方法中的代码行相同,我知道为每个按钮调用了正确的方法:

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

      如果我将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?

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

      詹姆斯

1 个答案:

答案 0 :(得分:0)

UITableView缓存值,以便它可以更高效(每次询问您可能会很慢 - 取决于您的计算方式)。

因此,如果您更改基础数据源,则需要让tableView了解它。您可以执行完整[self.tableView reloadData];或使用以下方法

  

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

这些通常在

的调用中使用
  

- beginUpdates
   - endUpdates

检查docs