Как остановить UITableView moveRowAtIndexPath от оставления пустых строк при переупорядочении - PullRequest
3 голосов
/ 11 февраля 2010

У меня возникла проблема, при которой при переупорядочении моих UITableViewCells tableView не прокручивается с ячейкой. Появляется только пустая строка, и любая последующая прокрутка приводит к ошибке «Массив вне границ» без какого-либо моего кода в трассировке стека. Вот краткое видео проблемы.

Вот соответствующий код:

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

А вот сгенерированный Stack Trace аварии:

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 ]

1 голос
/ 13 февраля 2010

ТАК ... На самом деле проблема в рамках 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];
    }
  }
}

Просто прокомментируйте их, и все будет работать нормально. Я не знаю, разрушит ли это что-нибудь где-нибудь, но, по крайней мере, вы знаете, где проблема.

0 голосов
/ 26 апреля 2010

Перекрестная публикация моего ответа на этот связанный вопрос :

Я просто столкнулся с той же проблемой в моем приложении.

Ситуация такова, что у меня есть два раздела таблицы. Элементы можно перетаскивать внутри и между разделами. Пользователи могут перетаскивать ячейки в любую строку в первом разделе, но во втором разделе элементы сортируются, поэтому для любой данной ячейки есть только одна действительная строка.

Если я прокручиваю вид так, чтобы нижняя часть секции 1 и верхняя часть секции 2 были видны, возьмите элемент в секции 1, который сортируется по нижней части секции 2, и перетащите его в верхнюю часть секции 2, Мой tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath: метод вызывается, и я возвращаю правильную позицию назначения, которая находится на несколько строк ниже нижней части экрана. В пользовательском интерфейсе вы можете увидеть пустую ячейку, созданную в нижней части экрана, которая не является правильной строкой назначения.

Когда вы отпустите ячейку, та поддельная ячейка, которая была создана в нижней части экрана (в середине раздела 2), останется там! tableView:cellForRowAtIndexPath: никогда даже не вызывается за это. Как только вы пытаетесь что-то сделать с этой камерой, вы терпите крах.

Моим первым решением было просто вызвать [tableView reloadData] в конце tableView:moveRowAtIndexPath:toIndexPath:. Но это вызывает сбой, поэтому вместо задержки я вызываю его косвенно. Но есть еще одна ошибка: после отложенного вызова 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];
}

Обратите внимание, что все еще есть ошибка пользовательского интерфейса. На экране перетаскиваемая ячейка анимируется в поддельную пустую ячейку. В конце анимации пустая ячейка перерисовывается с правильными данными для этой строки, но наблюдающий пользователь заметит, что перетаскиваемая ячейка оживляется в неправильное место, а затем мгновенно преобразуется в другую ячейку.

Это определенно глупый пользовательский интерфейс. Я подумал о прокрутке нужной строки назначения на экране, но если бы я сделал это, она бы заполнила экран вторым разделом, и любая попытка перетаскивания назад к первому разделу будет постоянно срываться моей (теперь раздражающей) автоматической прокруткой. Возможно, мне придется изменить пользовательский интерфейс, но для этого потребуются некоторые сложные и надоедливые изменения в моей модели данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...