iOS: предварительный расчет высоты строк для UITableView - PullRequest
5 голосов
/ 22 марта 2012

Я боролся с этим в течение нескольких месяцев в своем проекте.

Вот предложение: у меня UITableView только с 1 секцией, но высота UITableViewCells различна.Каждый UITableViewCell представляет объект, и новые объекты постоянно становятся доступными и вставляются в UITableView.Единственный способ эффективно определить высоту нового объекта, который будет отображаться в таблице, - это нарисовать его один раз (запустите код, который рисует UITableViewCell, и посмотрите, какова полученная высота).

Ах,но есть проблема!Метод делегата UITableView tableView: heightForRowAtIndexPath: вызывается ДО того, как ячейка отрисовывается впервые!Это имеет смысл: таблица хочет расположить ячейки перед их рисованием.

Я экспериментировал с LOTS и могу найти только 2 варианта, оба из которых имеют серьезные недостатки:

  1. Если новый объект, который будет показан в таблице, ранее не был виден, просто «угадать» высоту и измените размер ячейки позже (после того, как ячейка будет нарисована) по мере необходимостиОднако это вызывает проблемы с прокруткой, так как, когда пользователь прекращает прокрутку, может случиться так, что размер одной или нескольких ячеек должен быть изменен до правильной высоты, что создает плохой пользовательский опыт, поскольку таблица перемещается и реорганизуется.
  2. Предварительный расчет высоты объекта путем «тестирования» объекта перед его вставкой в ​​UITableView.Когда новый объект станет доступным, создайте «фиктивный» UITableViewCell и нарисуйте его за пределами экрана, рассчитав таким образом высоту объекта и сохранив его для дальнейшего использования.Проблема в том, что, конечно, мы можем выполнять тестирование высоты только в главном потоке (поскольку нам нужно рисовать), что вызывает значительное отставание в приложении.

Мое решение до сих пориспользовался # 2, но попытался облегчить проблему, сделав так, чтобы тесты высоты выполнялись только каждые 1/4 секунды, что помогает «разнести» тестирование так, чтобы основной поток незаперт слишком много.Но решение не элегантное и все еще вызывает проблемы с запаздыванием, особенно на старых устройствах.Лаг достаточно плох, поэтому мне действительно нужно лучшее решение.

Ответы [ 4 ]

2 голосов
/ 22 марта 2012

Для расчета высоты, , это и , этот ответ предполагают, что вы можете рисовать вне экрана в фоновом потоке, используя CGLayer. Ссылка на документацию iOS для CGLayer .

И для кэширования высоты в зависимости от некоторого идентификатора данных, отображаемого в ячейке, вы можете использовать код, подобный этому:

-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
    NSString* objectId = [self getObjectIdForPath:indexPath];
    if (![self->idToCellHeight objectForKey: objectId])
    {
        CGFloat height = [object calculateHeightForId:objectId];
        [self->idToCellHeight setObject:[NSNumber numberWithFloat:height] forKey: objectId];
    }

    return [[self->idToCellHeight objectForKey:objectId] floatValue];
}

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

1 голос
/ 20 июня 2013

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

В качестве альтернативы закадровому рисованию, совершенно нормально использовать методы sizeThatFits таких классов, как UILabel для определения высоты.

Предостережение заключается в том, что почти все UIView связанные методы должны выполняться в главном потоке .

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

Вид размеров можно создать из основного потока:

UILabel *mySizingLabel = [[UILabel alloc] init];

Однако при определении размера это должно быть сделано в основном потоке.Будьте осторожны, чтобы не использовать dispatch_async или [[NSOperationQueue] mainQueue].Это, например, не будет работать :

__block CGSize size;
dispatch_async(dispatch_get_main_queue(), ^{
    size = [mySizingLabel sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
}];

CGFloat myCellHeight = size.height;

Это потому, что строка, которая назначает myCellHeight, будет выполняться перед sizeThatFits, тем самым давая бессмысленное значение.

Вместо этого необходимо дождаться в главном потоке, пока не вернется sizeThatFits.Это можно сделать с помощью performSelectorOnMainThread с wait, установленным на YES.Параметры могут передаваться в и из этого метода с использованием NSMutableArray.

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

Еще одна вещь, не поддавайтесь искушению использовать дополнение UIKit sizeWithFont.Помимо проблем, отмеченных Майком Уэллером в «Вы делаете это неправильно # 2: определение размера меток с помощью - [NSString sizeWithFont: ...]» , применяется то же самое предостережение, что этот метод также должен быть выполненна основном:

NSString Справочник по дополнениям UIKit

Методы, описанные в этом расширении класса, должны использоваться из основного потока вашего приложения.

0 голосов
/ 13 января 2014

Я постараюсь кратко объяснить.

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

-(void)calculateAsyncRowHeights {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        UITableView *tableView=[self tableView];

        for(ManagedObject *o in _tableData) {
            CGFloat height=[self calculateRowHeight:tableView withObject:o]; //this is the method that calculates row heights. This is a seperate method because when necessary I call it in - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
            [o setRowHeight:height]; //I set rowHeight property of my model object
        }

        dispatch_async(dispatch_get_main_queue(), ^{

        });
    });
}

In - (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath проверяет, имеет ли объект модели высоту строки, уже рассчитанную. Если да, верните его, если нет, вычислите его как [self CalcuRowHeight: tableView withObject: o]; метод и вернуть его возвращаемое значение.

Он работает как шарм и решил все мои проблемы с производительностью.

0 голосов
/ 22 марта 2012

Мне кажется, что вы должны создать NSMutableArray, который содержит элементы NSNumber, которые соответствуют вычисленным высотам ячеек, которые существуют в таблице. Когда вы получите новую ячейку, которую нужно добавить в таблицу, вычислите высоту элемента, а затем вставьте высоту в изменяемый массив в правильном положении или, другими словами, в том же положении, в котором он будет занимать таблицу, а затем перезагрузите данные таблицы.

Таким образом, вам не нужно пересчитывать какие-либо высоты, вы просто ссылаетесь на объект по индексу в строке пути индекса.

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