代码之家  ›  专栏  ›  技术社区  ›  coneybeare

如何停止UITableView MoveRowatindexPath在重新排序时保留空行

  •  3
  • coneybeare  · 技术社区  · 15 年前

    我遇到了一个问题,在重新排序UITableViewCells时,TableView没有随单元格滚动。只有一个空行出现,任何随后的滚动都会得到一个数组越界错误,堆栈跟踪中没有任何代码。 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: 方法,因为它在被注释掉时仍然崩溃。

    2 回复  |  直到 15 年前
        1
  •  1
  •   tt.Kilew    15 年前

    所以…事实上,问题是在three20框架中。 作为UITableView子类的ttTableView有两种错误方法:

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    // 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];
        }
      }
    }
    

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

        2
  •  0
  •   Community CDub    8 年前

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

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

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

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

    当你放开手机时,屏幕底部(在第2节中间)创建的那个伪造手机就留在那里! tableView:cellForRowAtIndexPath: 从来没有人叫过它。一旦你想用那个手机做任何事情,你就崩溃了。

    我的第一个解决方案是在 tableView:moveRowAtIndexPath:toIndexPath: .但是这会导致崩溃,所以我在延迟之后间接地称之为崩溃。但接下来是 另一个 bug:在延迟的reloaddata调用之后, 表视图:moverowatindexpath:toindexpath: 再次被调用,请求将项目1移过第一个节的结尾,移到同一位置。所以,我不得不添加代码来忽略虚假的no op请求。

    下面是代码:

    - (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,但这需要对我的数据模型进行一些复杂而麻烦的更改。

    推荐文章