Есть ли способ обновить высоту одного UITableViewCell без перерасчета высоты для каждой ячейки?

У меня есть UITableView с несколькими разделами. В одном разделе содержатся ячейки, которые будут изменяться как пользовательский тип текста в UITextView. В другом разделе содержатся ячейки, которые отображают содержимое HTML, для которых расчет высоты относительно дорог.

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

[self.tableView beginUpdates];
[self.tableView endUpdates];

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

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

Обновление

Я все еще ищу решение для этого. Как я уже сказал, я пытался использовать reloadRowsAtIndexPaths:, но похоже, что это не сработает. Вызов reloadRowsAtIndexPaths: с хотя бы одной строкой будет по-прежнему вызывать heightForRowAtIndexPath: для каждой строки, даже если cellForRowAtIndexPath: будет вызываться только для запрошенной строки. На самом деле, это выглядит как любое время, когда строка вставлена, удалена или перезагружена, heightForRowAtIndexPath: вызывается для каждой строки в ячейке таблицы.

Я также попытался поместить код в willDisplayCell:forRowAtIndexPath:, чтобы вычислить высоту непосредственно перед тем, как ячейка появится. Чтобы это работало, мне нужно было заставить табличное представление повторно запросить высоту для строки после выполнения вычисления. К сожалению, вызов [self.tableView beginUpdates]; [self.tableView endUpdates]; из willDisplayCell:forRowAtIndexPath: вызывает исключение индекса за пределами исключения в внутреннем коде UITableView. Думаю, они не ожидают, что мы это сделаем.

Я не могу не чувствовать, что это ошибка в SDK, которая в ответ на [self.tableView endUpdates] не вызывает estimatedHeightForRowAtIndexPath: для ячеек, которые не видны, но я все еще пытаюсь найти какой-то вид обходного пути. Любая помощь приветствуется.

Ответ 1

Эта ошибка была исправлена ​​в iOS 7.1.

В iOS 7.0, похоже, не существует какой-либо проблемы вокруг этой проблемы. Вызов [self.tableView endUpdates] вызывает heightForRowAtIndexPath: для каждой ячейки таблицы.

Однако в iOS 7.1 вызов [self.tableView endUpdates] вызывает вызов heightForRowAtIndexPath: для видимых ячеек, а estimatedHeightForRowAtIndexPath: - для невидимых ячеек.

Ответ 2

Как отмечено, reloadRowsAtIndexPaths:withRowAnimation: приведет к тому, что представление таблицы спросит его UITableViewDataSource для нового представления ячейки, но не будет запрашивать UITableViewDelegate для обновленной высоты ячейки.

К сожалению, высота будет обновляться только путем вызова:

[tableView beginUpdates];
[tableView endUpdates];

Даже без каких-либо изменений между двумя вызовами.

Если ваш алгоритм вычисления высот слишком трудоемкий, возможно, вы должны кэшировать эти значения. Что-то вроде:

- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat height = [self cachedHeightForIndexPath:indexPath];

    // Not cached ?
    if (height < 0)
    {
        height = [self heightForIndexPath:indexPath];
        [self setCachedHeight:height
                 forIndexPath:indexPath];
    }

    return height;
}

И убедитесь, что reset эти высоты до -1, когда содержимое изменяется или во время инициализации.

Edit:

Также, если вы хотите как можно больше отложить вычисление высоты (пока они не прокручиваются), вы должны попробовать выполнить это (только для iOS 7+):

@property (nonatomic) CGFloat estimatedRowHeight

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

Значение по умолчанию равно 0, что означает отсутствие оценки.

Ответ 3

Переменные высоты строк оказывают очень негативное влияние на производительность вашего стола. Вы говорите о веб-контенте, который отображается в некоторых ячейках. Если мы не говорим о тысячах строк, думать о, реализующем ваше решение с помощью UIWebView, а не UITableView, стоит подумать. У нас была аналогичная ситуация, и мы пошли с UIWebView с настраиваемой HTML-разметкой, и она отлично работала. Как вы, наверное, знаете, у вас неприятная асинхронная проблема, когда у вас есть динамическая ячейка с веб-контентом:

  • После настройки содержимого ячейки вы должны
  • Подождите, пока веб-представление в ячейке не будет выполнено, показывая веб-контент,
  • тогда вам нужно зайти в UIWebView и - с помощью JavaScript - спросить документ HTML, насколько он высок
  • а затем обновите высоту UITableViewCell.

Никакой забавы вообще и много прыжков и дрожания для пользователя.

Если вам нужно пойти с UITableView, определенно кеш подсчитанные высоты строк. Таким образом, будет дешево вернуть их в heightForRowAtIndexPath:. Вместо того, чтобы сообщать UITableView, что делать, просто сделайте свой источник данных быстрым.

Ответ 4

Используйте следующий метод UITableView:

- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation

Вам нужно указать NSArray из NSIndexPath, который вы хотите перезагрузить. Если вы хотите перезагрузить только одну ячейку, тогда вы можете предоставить NSArray, который содержит только один NSIndexPath.

NSIndexPath* rowTobeReloaded = [NSIndexPath indexPathForRow:1 inSection:0];
NSArray* rowsTobeReloaded = [NSArray arrayWithObjects:rowTobeReloaded, nil];
[UITableView reloadRowsAtIndexPaths:rowsTobeReloaded withRowAnimation:UITableViewRowAnimationNone];

Ответ 5

Есть ли способ? Ответ - нет.

Для этого вы можете использовать только heightForRowAtIndexPath. Таким образом, все, что вы можете сделать, это сделать это как можно дешевле, например, сохраняя NSmutableArray ваших высот ячеек в вашей модели данных.

Ответ 6

У меня была аналогичная проблема (прокрутка прокрутки таблицы в любом изменении), потому что у меня был

  • (CGFloat) tableView: (UITableView *) tableView measuredHeightForRowAtIndexPath: (NSIndexPath *) indexPath { возврат 500; }

комментарий всей функции помог.

Ответ 7

Метод heightForRowAtIndexPath: всегда будет вызываться, но вот обходной путь, который я бы предложил.

Всякий раз, когда пользователь вводит текст UITextView, сохраните локальную переменную indexPath ячейки . Затем, когда вызывается heightForRowAtIndexPath:, проверьте значение сохраненного indexPath. Если сохраненный indexPath не nil, извлеките ячейку, которая должна быть изменена и сделайте это. Что касается других ячеек, используйте ваши кешированные значения. Если сохраненный indexPath равен nil, выполните ваши обычные строки кода, которые в вашем случае требуются.

Вот как бы я рекомендовал:

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...

    [textView setDelegate:self];
    [textView setTag:indexPath.row];

    ...
}

Затем в вашем методе делегата UITextView textViewDidChange: извлеките indexPath и сохраните его. savedIndexPath - локальная переменная.

- (void)textViewDidChange:(UITextView *)textView
{
    savedIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}

Наконец, проверьте значение savedIndexPath и выполните то, что ему нужно.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (savedIndexPath != nil) {
        if (savedIndexPath == indexPath.row) {
            savedIndexPath = nil;

            // return the new height
        }
        else {

            // return cached value
        }
    }
    else {
        // your normal calculating methods...
    }
}

Надеюсь, это поможет! Удачи.

Ответ 8

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

К сожалению, это решение работает только в том случае, если у вас есть весь ваш HTML-контент.