如何阻止UITableView moveRowAtIndexPath在重新排序时留下空白行

时间:2010-02-11 05:02:23

标签: iphone cocoa-touch uitableview crash

我遇到一个问题,在重新排序我的UITableViewCells时,tableView不会滚动单元格。只显示一个空行,并且任何后续滚动都会在Stack Trace中没有任何代码的情况下获得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

NOte表示数组越界长度不是我元素的长度(我有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:方法中,因为当它被注释掉时仍会崩溃。

2 个答案:

答案 0 :(得分: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];
    }
  }
}

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

答案 1 :(得分:0)

将我的回答交叉发布到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,但这需要对我的数据模型进行一些复杂而麻烦的更改。