【问题标题】:How to stop UITableView moveRowAtIndexPath from leaving blank rows upon reordering如何阻止 UITableView moveRowAtIndexPath 在重新排序时留下空白行
【发布时间】:2010-02-11 05:02:23
【问题描述】:

我遇到了一个问题,在重新排序我的 UITableViewCells 时,tableView 没有随着单元格滚动。只有一个空白行出现,并且任何后续滚动都会出现 Array out of bounds 错误,而堆栈跟踪中没有我的任何代码。 Here is a quick video of the problem.

以下是相关代码:

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return indexPath.section == 1;
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    BOOL ret = indexPath.section == 1 && indexPath.row < self.count;
    DebugLog(@"canMoveRowAtIndexPath: %d:%d %@", indexPath.section, indexPath.row, (ret ? @"YES" : @"NO"));
    return ret;
}
- (void)delayedUpdateCellBackgroundPositionsForTableView:(UITableView *)tableView {
    [self performSelectorOnMainThread:@selector(updateCellBackgroundPositionsForTableView:) withObject:tableView waitUntilDone:NO];
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    if (fromIndexPath.row == toIndexPath.row) return;

    DebugLog(@"Moved audio from %d:%d to %d:%d", fromIndexPath.section, fromIndexPath.row, toIndexPath.section, toIndexPath.row);
    NSMutableArray *audio = [self.items objectAtIndex:fromIndexPath.section];
    [audio exchangeObjectAtIndex:fromIndexPath.row withObjectAtIndex:toIndexPath.row];
    [self performSelector:@selector(delayedUpdateCellBackgroundPositionsForTableView:) withObject:tableView afterDelay:kDefaultAnimationDuration/3];
}

这是生成的崩溃堆栈跟踪:

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000002, 0x0000000000000000
Crashed Thread:  0  Dispatch queue: com.apple.main-thread

Application Specific Information:
iPhone Simulator 3.2 (193.3), iPhone OS 3.0 (7A341)
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFArray removeObjectsInRange:]: index (6) beyond bounds (6)'

Thread 0 Crashed:  Dispatch queue: com.apple.main-thread
0   CoreFoundation                  0x302ac924 ___TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION___ + 4
1   libobjc.A.dylib                 0x93cb2509 objc_exception_throw + 56
2   CoreFoundation                  0x3028e5fb +[NSException raise:format:arguments:] + 155
3   CoreFoundation                  0x3028e55a +[NSException raise:format:] + 58
4   Foundation                      0x305684e9 _NSArrayRaiseBoundException + 121
5   Foundation                      0x30553a6e -[NSCFArray removeObjectsInRange:] + 142
6   UIKit                           0x30950105 -[UITableView(_UITableViewPrivate) _updateVisibleCellsNow] + 862
7   UIKit                           0x30947715 -[UITableView layoutSubviews] + 250
8   QuartzCore                      0x0090bd94 -[CALayer layoutSublayers] + 78
9   QuartzCore                      0x0090bb55 CALayerLayoutIfNeeded + 229
10  QuartzCore                      0x0090b3ae CA::Context::commit_transaction(CA::Transaction*) + 302
11  QuartzCore                      0x0090b022 CA::Transaction::commit() + 292
12  QuartzCore                      0x009132e0 CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 84
13  CoreFoundation                  0x30245c32 __CFRunLoopDoObservers + 594
14  CoreFoundation                  0x3024503f CFRunLoopRunSpecific + 2575
15  CoreFoundation                  0x30244628 CFRunLoopRunInMode + 88
16  GraphicsServices                0x32044c31 GSEventRunModal + 217
17  GraphicsServices                0x32044cf6 GSEventRun + 115
18  UIKit                           0x309021ee UIApplicationMain + 1157
19  XXXXXXXX                        0x0000278a main + 104 (main.m:12)
20  XXXXXXXX                        0x000026f6 start + 54

请注意,超出范围的数组长度不是我的元素的长度(我有 9 个),但总是更小。

我已经尝试解决这个问题很多 小时 天但无济于事......有什么想法吗?



更新:根据要求提供更多代码
在我的代表中:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewCellEditingStyleNone;
}

- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
    int count = [(UAPlaylistEditDataSource *)self.dataSource count];
    if (proposedDestinationIndexPath.section == 0) {
        return [NSIndexPath indexPathForRow:0 inSection:sourceIndexPath.section];
    }else if (proposedDestinationIndexPath.row >= count) {
        return [NSIndexPath indexPathForRow:count-1 inSection:sourceIndexPath.section];
    }
    return proposedDestinationIndexPath;
}

……就是这样。我正在使用 three20 框架,到目前为止,我在重新排序方面没有任何问题。问题也不在于updateCellBackgroundPositionsForTableView: 方法,因为当它被注释掉时它仍然崩溃。

【问题讨论】:

  • 你能分辨出是哪一行导致了崩溃吗?发生崩溃时提供调用堆栈。您确定它与您的 updateCellBackgroundPositionsForTableView 方法没有关联吗?
  • 堆栈跟踪不包含我的任何代码(并不是说我没有导致它),但那里没有关于代码行的有用信息。无论如何我都会更新上面的堆栈跟踪
  • 可以添加 updateCellBackgroundPositionsForTableView 的代码吗?和其他 UITableViewDataSource 相关的方法?我还有一个建议。我认为您在创建/重用单元时遇到问题...需要更多代码...更多代码... :)
  • 我没有更多的代码给你看……我正在使用优秀的three20框架。此外,崩溃肯定不在 u​​pdateCellBackgroundPositionsForTableView 方法中,因为当它被注释掉时它仍然崩溃。
  • 我已经在我的项目中添加了你所有的方法并且一切正常。问题出在此数据源的另一种方法中。或者也许在 TableViewDelegate 中。

标签: iphone cocoa-touch uitableview crash


【解决方案1】:

所以...实际上问题出在 Three20 框架中。 TTTableView 是 UITableView 的子类,有两个错误的方法:

///////////////////////////////////////////////////////////////////////////////////////////////////
// UIScrollView

- (void)setContentSize:(CGSize)size {
  if (_contentOrigin) {
    CGFloat minHeight = self.height + _contentOrigin;
    if (size.height < minHeight) {
      size.height = self.height + _contentOrigin;
    }
  }

  CGFloat y = self.contentOffset.y;
  [super setContentSize:size];

  if (_contentOrigin) {
    // As described below in setContentOffset, UITableView insists on messing with the 
    // content offset sometimes when you change the content size or the height of the table
    self.contentOffset = CGPointMake(0, y);
  }
}

- (void)setContentOffset:(CGPoint)point {
  // UITableView (and UIScrollView) are really stupid about resetting the content offset
  // when the table view itself is resized.  There are times when I scroll to a point and then
  // disable scrolling, and I don't want the table view scrolling somewhere else just because
  // it was resized.  
  if (self.scrollEnabled) {
    if (!(_contentOrigin && self.contentOffset.y == _contentOrigin && point.y == 0)) {
      [super setContentOffset:point];
    }
  }
}

只要评论他们,一切都会好起来的。 我不知道这是否会在某个地方破坏某些东西,但至少,你知道问题出在哪里。

【讨论】:

  • 谢谢!在您的指导下,我实际上已将其进一步隔离。问题是在重新排序行时,self.scrollEnabled 为 NO,因此不调用超级行。我将不得不研究为什么这样做(我确信这是有原因的),但它可能太模糊了,我可以改回来。
【解决方案2】:

将我的答案交叉发布到this related question:

我只是在我的应用程序中遇到了我认为相同的问题。

情况是我有两个表格部分。可以在部分内和部分之间拖动项目。用户可以将单元格拖动到第一部分中的任何行,但在第二部分中,项目是排序的,因此对于任何给定的单元格,只有一个有效行。

如果我滚动视图以使第 1 部分的底部和第 2 部分的顶部可见,请在第 1 部分中抓取一个排序到第 2 部分底部的项目,然后将其拖到第 2 部分的顶部,我的tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath: 方法被调用,我返回正确的目标位置,即屏幕底部下方的几行。在 UI 中,您可以看到在屏幕底部创建了一个空单元格,这不是正确的目标行。

当您放开该单元格时,在屏幕底部(在第 2 部分的中间)创建的那个虚假单元格将保留在那里! tableView:cellForRowAtIndexPath: 甚至从未被要求这样做。只要您尝试对该单元格执行任何操作,就会崩溃。

我的第一个解决方案是在tableView:moveRowAtIndexPath:toIndexPath: 的末尾调用 [tableView reloadData]。但这会导致崩溃,所以我在延迟后间接调用它。但是还有另一个错误:在延迟 reloadData 调用后,tableView:moveRowAtIndexPath:toIndexPath: 再次被调用,并发出虚假请求,将项目从第一部分的末尾移到同一位置。所以,我不得不添加代码来忽略虚假的无操作请求。

所以,这里是代码:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)pathSrc toIndexPath:(NSIndexPath *)pathDst
{
  // APPLE_BUG: after doing the delayed table reload (see below), we get a bogus
  // request to move a nonexistant cell to its current location
  if (pathSrc.row == pathDst.row && pathSrc.section == pathDst.section)
    return;

  // update your data model to reflect the move...

  // APPLE_BUG: if you move a cell to a row that's off-screen (because the destination
  // has been modified), the bogus cell gets created and eventually will cause a crash
  [self performSelector:@selector(delayedReloadData:) withObject:tableView afterDelay:0];
}

- (void)delayedReloadData:(UITableView *)tableView
{
  Assert(tableView == self.tableView);
  [tableView reloadData];
}

请注意,仍然存在 UI 错误。在屏幕上,拖动的单元格被动画化到虚假的空单元格中。在动画结束时,空单元格会使用该行的正确数据重新绘制,但细心的用户会注意到拖动的单元格被动画到错误的位置,然后立即变形到另一个单元格。

这绝对是一个愚蠢的用户界面。我考虑将正确的目标行滚动到屏幕上,但如果我这样做,它将用第二部分填充屏幕,然后任何拖回第一部分的尝试都会被我的(现在很烦人的)自动滚动不断地阻挠。我可能需要更改 UI,但这需要对我的数据模型进行一些复杂且麻烦的更改。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2017-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-03
  • 2015-01-27
  • 1970-01-01
相关资源
最近更新 更多