insertRowsAtIndexPaths, вызывающий cellForRowAtIndexPath для каждой строки

#iphone #ios #uitableview

#iPhone #iOS #uitableview

Вопрос:

Я пытаюсь вставить кучу строк в пустой UITableView за один шаг, используя insertRowsAtIndexPaths. (Я знаю, что это звучит не очень полезно, но это упрощенная версия того, что мне действительно нужно сделать.)

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

Это не кажется правильным. И для меня это практически бесполезно, если он это делает.

Единственный немного странный артефакт, который я вижу, заключается в том, что когда я делаю это, кажется, что это действительно анимирует строки на место. Это не withRowAnimation, поскольку для него установлено значение none. Но, похоже, здесь используется какая-то концепция анимации более высокого уровня. У меня была идея, что, когда он анимировал строки на место, он думал, что ему нужны ячейки для большего количества / всех строк, пока они не будут удалены с экрана. Но я не могу отключить эту анимацию, просто не используя reloadData, чего я бы предпочел избежать.

Обычно у меня есть набор данных, который меняется за кулисами. Я могу тщательно просмотреть и сконструировать изменения для генерации данных для вызовов insert / delete / reload. Но я не могу ограничить это тем, что отображается на экране, поскольку тогда оно не соответствует поставщику данных.

Я полагаю, я, должно быть, что-то упускаю… есть идеи?

(Я делаю это в паре beginUpdates / endUpdates, но, похоже, это не имеет значения.)

Комментарии:

1. Звучит как ошибка. Отправьте отчет об ошибке: bugreport.apple.com

2. Можете ли вы создать простой проект / код, который мы можем протестировать и который дублирует поведение? Звучит действительно так, как будто это может быть ошибка.

3. Да, я думаю, это следующий шаг, если это что-то неизвестное.

4. @smparkes как только вы отправите отчет, пожалуйста, оставьте здесь комментарий с номером ошибки. 🙂

5. @Dave DeLong, все так говорят, но на радаре я никогда не могу понять, как сослаться на другую ошибку. Есть идея? Или просто оставить # в теле текста?

Ответ №1:

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

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

Похоже, для этого не существует какой-либо альтернативной реализации, которая работает во всех случаях. Рекомендуемое решение для моего случая — «солгать»: выяснить, какие ячейки будут существовать на странице после анимации, и вставить их. После завершения этой анимации вставьте оставшиеся ячейки, которые не будут отображаться, и таблица не будет вызывать cellForRowAtIndexPath .

Для этого требуется реализовать объект между табличным представлением и «реальным» источником данных, который может «лгать» табличному представлению о количестве строк в таблице на время анимации. Болезненно, но, по-видимому, именно это и делалось в подобных случаях.

Комментарии:

1. «своего рода «расти вниз»» — Звучит так, как будто вы используете UITableViewRowAnimationTop или UITableViewRowAnimationBottom. Вместо этого вы могли бы попробовать анимацию -right, -left или -fade, поэтому не все ячейки видны в начале.

2. Нет. Это не анимация ячейки. Это анимация на уровне таблицы (подтверждена Apple) и происходит даже при использовании UITableViewRowAnimationNone: таблица по-прежнему анимирует ячейки, по крайней мере, в случае, когда в таблице нет строк для начала, только пустые заполнители. Единственный способ отключить это — отключить анимацию на уровне UIView, но тогда вы в значительной степени вернетесь к reloadData.

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

Ответ №2:

У меня была такая же проблема, что я сделал, так это объявил атрибут bMassiveLoad, инициализированный значением false. Перед тем, как выполнить «массивную загрузку», я установил для него значение true, и первое, что я делаю в cellForRowAtIndexPath, это проверяю наличие этого атрибута. В конце «масивной загрузки» я снова устанавливаю для него значение false и вызываю reloadData …

Я знаю, что это не красивый ответ, но он помогает мне.

Комментарии:

1. Это в основном то, что я тоже сделал. Мгновенная повторная загрузка данных превосходит зависание пользовательского интерфейса на полсекунды, пока таблица пытается выделить / инициализировать еще сотню ячеек таблицы.

Ответ №3:

Я смог решить это, объединив несколько ответов отсюда:

установите логическое значение массивной загрузки по мере необходимости, как предложено pocjoc

 - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    UITableView *tableView = self.tableView;
    switch(type) {
        case NSFetchedResultsChangeInsert:
            massiveLoad = YES;
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
        ...
    }
}
  

и перезагрузите видимые строки, когда это закончится

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
    if (massiveLoad) {
        massiveLoad = NO;
        [self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationFade];
    }
}
  

Ответ №4:

UITableView вызывает соответствующие методы делегата и источника данных сразу после вызова insertRowsAtIndexPaths, чтобы получить ячейки и другое содержимое для видимых ячеек. Это независимо от того, установлен ли параметр animated в значение YES или NO.

Смотрите обсуждение здесь

Если вы хотите, чтобы TableView обновлял только видимые ячейки, тогда просто обновите свой источник данных и вызовите [TableView reloadData].

Комментарии:

1. Моя проблема в том, что он вызывает cellForRowAtIndexPath для ячеек, которые не видны, по крайней мере, после завершения анимации вставки. (И я не уверен, о каком animated: YES / NO вы говорите.)

Ответ №5:

Я не знаю, является ли поведение, которое вы описываете, правильным, но UITableView у beginUpdates есть два метода: endUpdates и (здесь cellForRowAtIndexPath ), которые предназначены для оптимизации перерисовки (чтобы анимация выполнялась только в конце, в противном случае они выполняются один за другим, и это, возможно, может привести к вызову в ,,).

Я не уверен, что это могло бы вам помочь, но, возможно, вы можете попробовать…

Комментарии:

1. Я забыл упомянуть об этом, но я делаю это в паре начало / конец. Однако, похоже, это не имеет никакого значения.

Ответ №6:

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

Итак, что с этим делать. Вот как я решил это исправить. Когда я создавал TableView, я добавил ячейки в видимую область. Они были пустыми. Затем, когда я вставил новые строки, я добавил пути индекса только для дополнительных строк (т. Е. Если у меня есть 10 видимых строк и я хочу добавить 50 строк, включая видимые строки, я добавил только 40, поскольку первые 10 уже существуют). Это, конечно, оставляет вас с 10 пустыми ячейками. На этом этапе вы можете использовать:

 NSArray *indexPaths = [myTableView indexPathsForVisibleCells];
[myTableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
  

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

 if (![myDataArray count]) {
    // create a blank cell;
} else {
    // add context to your cells as you want;
}
  

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

Комментарии:

1. Мне нужно добавить ячейки. Не все будут видны.

2. О, я понимаю. Прошу прощения за путаницу. Я отредактирую свой ответ на то, что должно помочь с этим вместо этого

3. Мне действительно не нужно ничего обновлять. Я просто хочу вставить, скажем, 50 строк, но вызываю cellForRowAtIndexPath только для 10 отображаемых. Вы предлагаете вызвать reload * вместо insert *? Я не пробовал это (пока), но это звучит так, как будто я прошу его перезагрузить строки, которые не существуют.

4. Извините, я думал, вы говорите обратное, что у вас уже есть куча строк, но вы хотели обновить только те, которые видны. Итак, вы хотите добавить кучу, но показать только несколько?

5. Я просто пытаюсь получить нормальный рендеринг ячейки в контексте insert. Когда вы выполняете повторную загрузку данных, табличное представление вызывает cellForRowAtIndexPath только для строк на экране. Когда я вставляю строки, он вызывает cellForRowAtIndexPath для каждой вставляемой мной строки, отображаются они или нет. Это слишком дорого.

Ответ №7:

Для swift вы можете попробовать это

 self.mainTableView.beginUpdates()
    self.mainTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Fade)
    self.mainTableView.reloadRowsAtIndexPaths(self.mainTableView.indexPathsForVisibleRows!, withRowAnimation: UITableViewRowAnimation.Fade)
    self.mainTableView.endUpdates()
  

Ответ №8:

UITableView запросит видимые ячейки с помощью cellForRowAtIndexPath: метода, и это может сократить время обработки, когда ячеек много, например, 1k, и уменьшить расход памяти с помощью механизма повторного использования ячеек. Если вам нужно обработать все ячейки, то вам следует реализовать пользовательский метод для повторной обработки ваших исходных данных, а затем вызвать [tableView reloadData] для обновления пользовательского интерфейса.