Низкая производительность UITableViewCell с помощью AutoLayout
Я немного застрял с этим... любая помощь очень ценится. Я уже много времени отлаживал это.
У меня есть UITableView
с источником данных, предоставленным NSFetchedResultsController
. В отдельном контроллере просмотра я вставляю новые записи в CoreData с помощью [NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:]
, сохраняю контекст управляемого объекта и увольняю его. Очень стандартный материал.
Изменения в контексте управляемого объекта затем принимаются NSFetchedResultsController
:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
}
}
И здесь возникает проблема - для этого требуется слишком много времени (примерно 3-4 секунды на iPhone 4). И кажется, что время потрачено на расчет макета для ячеек.
Я удалил все из ячейки (включая пользовательский подкласс) и оставил ее только с UILabel
, но ничего не изменилось. Затем я изменил стиль ячейки на Basic (или ничего, кроме Custom), и проблема исчезла - новые ячейки добавляются мгновенно.
Я дважды проверял и NSFetchedResultsControllerDelegate
обратные вызовы вызываются только один раз. Если я игнорирую их и делаю [UITableView reloadSections:withRowAnimation:]
, ничего не меняется - он все еще очень медленный.
Мне кажется, что Auto Layout отключен для стилей ячейки по умолчанию, что делает их очень быстрыми. Но если это так - почему все быстро загружается, когда я нажимаю UITableViewController
?
Здесь трассировка вызова для этой проблемы:
![stack trace]()
Итак, вопрос в том, что здесь происходит? Почему клетки отображаются так медленно?
ОБНОВЛЕНИЕ 1
Я создал очень простое демонстрационное приложение, которое иллюстрирует проблему, с которой я сталкиваюсь. здесь источник - https://github.com/antstorm/UITableViewCellPerformanceProblem
Попробуйте добавить, по крайней мере, экранные ячейки, чтобы почувствовать проблемы с производительностью.
Также обратите внимание, что добавление строки непосредственно (кнопка "Вставить сейчас!" ) не вызывает какой-либо медлительности.
Ответы
Ответ 1
Хорошо, я наконец обошел эту проблему, не жертвуя анимацией. Мое решение - реализовать интерфейс UITableViewCell
в отдельном файле Nib с отключенным автозапуском. Для загрузки требуется немного больше времени, и вам нужно позиционировать subviews самостоятельно.
Вот код, который позволяет:
- (void)viewDidLoad {
[super viewDidLoad];
...
UINib *rowCellNib = [UINib nibWithNibName:@"RowCell" bundle:nil];
[self.tableView registerNib:rowCellNib forCellReuseIdentifier:@"ROW_CELL"];
}
Конечно, вам понадобится файл RowCell.nib с видом ячейки.
Пока нет решения исходной проблемы (что явно кажется ошибкой для меня), я использую этот файл.
Ответ 2
Верно, что Auto Layout может представить удар производительности. Однако в большинстве случаев это не заметно. Есть некоторые краевые случаи со сложными макетами, где избавление от него будет иметь значимое различие, но на самом деле это не проблема.
У меня нет хорошего объяснения, почему приложение ведет себя так, как есть, но, по крайней мере, у меня есть решение: не делайте обновления табличного представления, если представление таблицы не находится на экране. Это приводит к этому странному поведению.
Для этого вы можете, например, проверьте self.tableview.window != nil
в методах делегата выбранного контроллера результатов. Затем вам просто нужно добавить [self.tableview reloadData]
в viewWillAppear
, чтобы представление таблицы обновляло свои данные перед выходом на экран.
Надеюсь, что это поможет. И, пожалуйста, если у кого-то есть хорошее объяснение этого странного поведения, пожалуйста, дайте мне знать:)
Ответ 3
У меня такая же производительность в [table reloadData] в iOS 7.
В моем случае я решаю эту проблему, чтобы заменить код конфигурации ячейки с [cell layoutIfNeeded]
на следующий код:
[cell setNeedsUpdateConstraints];
[cell setNeedsLayout];
а затем верните ячейку. Кажется, все хорошо. Надеюсь, это может помочь другим людям с той же проблемой.
Ответ 4
Если вы все еще хотите использовать автоматическую компоновку в ячейках просмотра таблицы, есть способы сделать ее работоспособной. Это многоступенчатый процесс, но как только вы это сделаете, вы можете заставить механизм автоматического макета определять размер ячейки по размеру в дополнение к компоновке всего остального в ячейке.
Вы можете найти более подробную информацию здесь: Использование автоматической компоновки в UITableView для динамических раскладок ячеек и высоты строк в таблице, которая включает некоторые ярлыки, которые вы можете использовать, если находитесь на iOS 8.
Ответ 5
У меня были аналогичные проблемы с использованием сложного автоматического макета в подклассе uitableviewcell. Мое расположение ячеек зависит от данных, поступающих с сервера, но число состояний ячеек ограничено шестью. Поэтому каждый раз, когда я получаю nil от dequeueReusableCellWithIdentifier (создается новая ячейка), я устанавливаю динамический/пользовательский reuseIdentifier для только что созданной ячейки (self.dynamicReusableIdentifier). Геттер reuseIdentifier переопределяется в подклассе UITableViewCell (ACellSubclass):
- (NSString*) reuseIdentifier {
return self.dynamicReusableIdentifier;
}
Генерация строки self.dynamicReusableIdentifier основана на статическом методе (configureDynamicReuseIdentifier), определенном в классе ячеек (ACellSubclass). tableView: cellForRowAtIndexPath, затем выбирает правильный идентификатор повторного использования, возвращаемый с
cell = [tableView dequeueReusableCellWithIdentifier: [ACellSubclass configureDynamicReuseIdentifier: someData]];
а статические подзадачи повторно используемых элементов (метки, изображения) обновляются без каких-либо изменений автоматического макета.