Подтвердить что ты не робот

Низкая производительность 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

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

Также обратите внимание, что добавление строки непосредственно (кнопка "Вставить сейчас!" ) не вызывает какой-либо медлительности.

4b9b3361

Ответ 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]];

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