Прокрутка UITableView не является гладкой

17

У меня есть гладкая проблема прокрутки в моем UITableView с UITableViewCell, которая содержит UIImageView. Аналогичные проблемы можно найти по всему StrackOverflow, но ни одно из предлагаемых решений не помогло мне полностью избавиться от отставания.

Мой случай довольно распространен:

  1. изображения хранятся в хранилище приложений (в моем примере в комплекте приложений)
  2. изображения могут иметь разный размер (500x500, 1000x1000, 1500x1500).
  3. Мне нужно отобразить эти изображения в UITableView, где размер UIImageView равен 120x120 (сетчатка)

Я выполнил несколько советов по оптимизации и смог оптимизировать прокрутку. К сожалению, он все еще не идеален. Это мой сценарий:

  1. сначала я переместил всю логику загрузки / обработки / изменения размера изображения в фоновый поток
  2. Повторное использование UITableViewCell включено
  3. Как только UITableViewCell находится в поле зрения, я очищаю старые значения (настройки до нуля) и запускаю фоновый поток для загрузки изображения.
  4. В этот момент мы находимся в фоновом потоке, и я добавляю задержку в 500 мс, чтобы часто не менять настройки нового изображения (в случае быстрого прокрутки) (см. ниже объяснение).
  5. если UIImage существует в кеше статического изображения (обычный словарь с экземплярами UIImage) - выберите его и перейдите к шагу 9.
  6. if not - загрузить новое изображение из пакета (imageWithName), используя URL-адрес приложения (в реальных сценариях изображения будут сохранены в хранилище приложений, а не в комплекте)
  7. После загрузки изображения изменение размера до 120x120 с использованием графического контекста
  8. сохранить измененное изображение в кеш статического изображения
  9. В этот момент мы имеем экземпляр для UIImage, и процесс находится в фоновом потоке. Отсюда мы возвращаемся к потоку пользовательского интерфейса с данным изображением
  10. , если был очищен контекст данных (например, UITableViewCell исчез или повторно использовался для отображения другого изображения), мы пропускаем обработку текущего доступного изображения.
  11. , если контекст данных одинаков - назначьте UIImage UIImageView с альфа-анимацией (UIView.Animate)
  12. как только UITableViewCell выходит из поля зрения - очистите контекст данных

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

Есть еще две проблемы:

  1. в какой-то момент во время прокрутки у меня все еще есть небольшое отставание (на момент, когда я назначаю новый UIImage UIImageView.
  2. (это более заметно), когда вы нажимаете на элемент и возвращаетесь от деталей, есть задержка перед завершением анимации назад.

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

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

Плавное тестовое приложение для прокрутки

    
задан Alexey Strakh 11.10.2014 в 05:27
источник
  • Хотелось бы взглянуть на проект, но я использую xCode. Есть ли способ конвертировать? –  carlodurso 23.10.2014 в 21:44
  • Что инструменты (или эквивалент Xamarin) рассказывают о производительности вашего приложения? Где вы проводите время? Связана ли платформа с процессором или GPU? и т.п. –  David Rönnqvist 23.10.2014 в 21:49
  • Не похоже, что вы используете CoreData. Можете проверить, будет ли отображение эскизов в CoreData TableView не улучшать управление памятью и производительность кэширования. –  PhillipOReilly 23.10.2014 в 21:52
  • @carlodurso сопоставляется один-к-одному объективному –  Alexey Strakh 23.10.2014 в 22:59
  • @ Инструменты DavidRönnqvist показывают нормальное распределение активности (временные меры). Как измерить «Связано ли это с CPU или GPU?» –  Alexey Strakh 23.10.2014 в 22:59
Показать остальные комментарии

6 ответов

10

Вы пытались заполнить свой TableView только одним изображением 120x120, которое сохраняется в вашем Bundle ? Таким образом, вы можете проверить, возникает ли проблема с вашим Image rendering

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

В нашем последнем проекте у нас был UICollectionView с обложками книг. Большинство обложек находились между 400-800 килобайтами и чувство, в то время как прокрутка была очень плохая. Таким образом, мы создали миниатюру для каждого изображения (уменьшилось около 40-50 килобайт) и использовали эскизы вместо реальных обложек. Работает как шарм! Я добавил функцию создания эскизов

- (BOOL) createThumbnailForImageAtFilePath:(NSString *)sourcePath withName:(NSString *)name {

    UIImage* sourceImage = [UIImage imageWithContentsOfFile:sourcePath];
    if (!sourceImage) {
        //...
        return NO;
    }

    CGSize thumbnailSize = CGSizeMake(128,198);

    float imgAspectRatio = sourceImage.size.height / sourceImage.size.width;
    float thumbnailAspectRatio = thumbnailSize.height/thumbnailSize.width;

    CGSize scaledSize = thumbnailSize;

    if(imgAspectRatio >= thumbnailAspectRatio){
         //image is higher than thumbnail
         scaledSize.width = scaledSize.height * thumbnailSize.width / thumbnailSize.height;
    }
    else{
        //image is broader than thumbnail
        scaledSize.height = scaledSize.width * imgAspectRatio;
    }

    UIGraphicsBeginImageContextWithOptions( scaledSize, NO, 0.0 );
    CGRect scaledImageRect = CGRectMake( 0.0, 0.0, scaledSize.width, scaledSize.height );
    [sourceImage drawInRect:scaledImageRect];
    UIImage* destImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    NSString* thumbnailFilePath = [[self SOMEDIRECTORY] stringByAppendingPathComponent:name];

   BOOL success = [UIImageJPEGRepresentation(destImage, 0.9) writeToFile:thumbnailFilePath atomically:NO];

return success;

}     

ответ дан longilong 29.10.2014 в 10:34
  • Вы можете поделиться кодом Swift? –  Abhi 13.10.2016 в 08:47
  • нет, но вы можете перенести его. просто еще один синтаксис –  longilong 28.10.2016 в 17:33
3

Попробуйте библиотеку Async Display в facebook.

Ссылка

Действительно прост в использовании .. от их руководства: Ссылка

_imageNode = [[ASImageNode alloc] init];
_imageNode.backgroundColor = [UIColor lightGrayColor];
_imageNode.image = [UIImage imageNamed:@"hello"];
_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f);
[self.view addSubview:_imageNode.view];

Это декодирует изображение в фоновом потоке.

Я не уверен, легко ли использовать библиотеки iOS на Xamarin, но если это легко, дайте это сделать.     

ответ дан Krys Jurgowski 28.10.2014 в 03:41
2

Я подкатегорием CoreDataTableViewController Пола Хегарти и использую миниатюры моих фотографий в CoreDataTableView.

Ищите примеры в лекции 14 под названием FlickrFetcher и Photomania , Вам также потребуется загрузить CoreDataTableViewController по той же самой ссылке.

Создайте объект CoreData с соответствующим заголовком и определите любые атрибуты (переменные данных), которые вы хотите. Вам нужно будет определить два атрибута «Трансформируемый», один для фотографии и один для миниатюры.

Затем загрузите миниатюру в CoreDataTableView:

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

{     NSArray * exceptions = [NSArray arrayWithObjects: @ "SCR", @ "DNS", @ "NT", @ "ND", @ "NH", nil];

static NSString *CellIdentifier = @"resultsDisplayCell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

MarksFromMeets *athleteMarks = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString* date = [ITrackHelperMethods dateToAbbreviatedString:athleteMarks.meetDate];
NSMutableString *title = [NSMutableString stringWithFormat:@"%@", athleteMarks.markInEvent];
NSMutableString *subTitle = [NSMutableString stringWithFormat:@"%@ - %@",date, athleteMarks.meetName];
[title replaceOccurrencesOfString:@"(null)"
                       withString:@""
                          options:0
                            range:NSMakeRange(0, [title length])];

// cell.imageView.image = athleteMarks.photoThumbNail; // Don't like image in front of record.

[cell.textLabel setFont:[UIFont
                         fontWithName:@"Helvetica Neue" size:18]];

[cell.detailTextLabel setFont:[UIFont fontWithName:@"Helvetica Neue" size:16]];
[cell.detailTextLabel setTextColor:[UIColor grayColor]];

// make selected items orange
if ([athleteMarks.eventPR integerValue] != 0
    && (![exceptions containsObject:athleteMarks.markInEvent])) {

    title = [NSMutableString stringWithFormat:@"%@      (PR)",title];
    [cell.textLabel setTextColor:[UIColor redColor]];
}
else if ([athleteMarks.eventSB integerValue] != 0
         && (![exceptions containsObject:athleteMarks.markInEvent])) {
    title = [NSMutableString stringWithFormat:@"%@      (SB)",title];

    [cell.textLabel setTextColor:[UIColor orangeColor]];
} else {
    [cell.textLabel setTextColor:[UIColor grayColor]];
}

cell.textLabel.text = title;
cell.detailTextLabel.text = subTitle;

cell.indentationLevel = indentationLevelOne;
cell.indentationWidth = indentationForCell;

return cell;

}

Если вы хотите, я могу отправить вам пример подкаталога NSManagedObject для категории. Эта категория загружает фотографию и миниатюру в объект CoreData. Первый раз будет медленным. Однако после этого пользователь должен иметь возможность прокручивать TableView плавно, а затем все обновленные результаты будут загружаться автоматически. Дайте мне знать.

Хорошо, что CoreData обрабатывает все управление памятью.

Удачи!

    
ответ дан PhillipOReilly 25.10.2014 в 19:41
  • P.S. Тим Родли имеет большой набор руководств по CoreData по URL его имени, а также некоторые полезные вспомогательные классы. –  PhillipOReilly 25.10.2014 в 20:19
0

У меня недостаточно комментариев для комментариев. Итак, вот ответ, который помог мне прокрутить производительность таблицы:

  • Сделать высоту таблицы больше, чем окно с возможностью просмотра. Ячейки будут загружаться «вне экрана» и улучшать гладкость прокрутки.
  • Сделайте обработку изображений следующим способом: %код%

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

    
ответ дан roninSTI 23.10.2014 в 22:30
0

Возможно, вы могли бы попробовать SDWebImage . Это также компонент xamarin  который настраивает асинхронный загрузчик изображений и асинхронную память и кэширование образа диска с автоматическим управлением истечением кеша. Использование этого, вероятно, означало бы выбросить много сложного письменного кода, но, возможно, это стоит того, что ваш код станет намного проще. В iOS вы также можете установить SDWebImageManager внутри viewDidLoad контроллера:

- (void)viewDidLoad{
...
SDWebImageManager *manager = [SDWebImageManager sharedManager];
manager.delegate = self;
...
}

и установите контроллер представления в качестве делегата. Затем, когда вызывается следующий метод делегата:

- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL

вы можете масштабировать изображения до больших размеров до их кеширования.

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

    
ответ дан Don Miguel 29.10.2014 в 16:08
0

Weel У меня была аналогичная проблема, мой свиток не был гладким. Я вставляю в таблицу переменную UIImageView с внутренними labelViews. Я сделал, чтобы изменить метод HeightforRowAtIndexPath для оценочногоHeightforRowAtIndexPath, и теперь прокрутка является гладкой.     

ответ дан mapkiller 02.07.2015 в 12:36