iPhone - Асинхронное изображение отображается только при прокрутке

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

У меня есть большой массив данных, извлеченных из Интернета, и я использую ABTableViewCell, чтобы он работал плавно, отрисовывая все внутри contentView каждой ячейки, чтобы избежать замедления прокрутки UILabels и UIImageViews.

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

Сейчас таблица ведет себя так:

  • Загрузить: текст отображается, изображений нет
  • Прокрутка вверх или вниз: изображения, наконец, появляются, когда ячейки уходят с экрана.

Образец проекта находится здесь.

Код:

ABTableViewCell.h

@interface ABTableViewCell : UITableViewCell
{
    UIView *contentView;
}

ABTableViewCell.m

- (void)setNeedsDisplay
{
    [contentView setNeedsDisplay];
    [super setNeedsDisplay];

}

- (void)drawContentView:(CGRect)r
{
    // subclasses implement this
}

TableCellLayout.h

#import "ABTableViewCell.h"

@interface TableCellLayout : ABTableViewCell {

}

@property (nonatomic, copy) UIImage *cellImage;
@property (nonatomic, copy) NSString *cellName;

TableCellLayout.m

#import "TableCellLayout.h"

@implementation TableCellLayout

@synthesize cellImage, cellName;

- (void)setCellName:(NSString *)s
{
    cellName = [s copy];
    [self setNeedsDisplay];
}

- (void)setCellImage:(UIImage *)s
{
    cellImage = [s copy];
    [self setNeedsDisplay];
}

- (void)drawContentView:(CGRect)r
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextFillRect(context, r);

    [cellImage drawAtPoint:p];
    [cellName drawAtPoint:p withFont:cellFont];
}

TableViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil)
    {
        cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    cell.cellName = [[array valueForKey:@"name"] objectAtIndex:indexPath.row];
    cell.cellImage = [UIImage imageNamed:@"placeholder.png"]; // add a placeholder    

    NSString *imageURL = [[array valueForKey:@"imageLink"] objectAtIndex:indexPath.row];
    NSURL *theURL = [NSURL URLWithString:imageURL];

    if (asynchronousImageLoader == nil){
        asynchronousImageLoader = [[AsynchronousImages alloc] init];
    }
       [asynchronousImageLoader loadImageFromURL:theURL];

        cell.cellImage = asynchronousImageLoader.image;

return cell;
}

Это последний метод, который AsynchronousImageLoader вызывает после подготовки изображения:

- (void)setupImage:(UIImage*)thumbnail {
    self.image = thumbnail;
        [self setNeedsLayout];

}

Мне просто нужен правильный способ, чтобы мои видимые ячейки перерисовывались после загрузки изображения строки. Я полагаю, что мне нужно что-то добавить в этот последний метод (setupImage), но я не могу заставить его работать так, как должно. Мысли? Большое спасибо!

Окончательная правка: решение

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

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

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

AsyncImageView.m

 - (void)setupImage:(UIImage*)thumbnail {

      self.cellImage = thumbnail;
[cell setNeedsDisplay];

}

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

Затем в моем cellForRowAtIndexPath:

NSURL *theURL = [NSURL URLWithString:imageURL];

AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];

[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;

return cell;

Возможно, были еще одна или две настройки, но это были основные проблемы. Еще раз спасибо сообществу SO!


person Community    schedule 16.08.2012    source источник
comment
ты решил это? Я обновил свой ответ ниже 2 дня назад с помощью кода, который, как я тестировал, работает. Пожалуйста, проверьте детали и подтвердите, что проблема решена. Спасибо!   -  person ikuramedia    schedule 19.08.2012
comment
Привет, да, сообщество снова стало потрясающим. Я приму ответы и напишу краткое редактирование, чтобы показать, что я сделал, чтобы все это работало. Спасибо за помощь!   -  person    schedule 19.08.2012
comment
Рад слышать, рад помочь!   -  person ikuramedia    schedule 19.08.2012


Ответы (5)


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

if(cell) {

    dispatch_async(dispatch_get_main_queue(), ^{

        cell.cellImage = thumbnail;
        [cell setNeedsDisplay];

    });

}

[РЕДАКТИРОВАТЬ]

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

......
    AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
    asynchronousImageLoader.delegate = cell;
    [asynchronousImageLoader loadImageFromURL:theURL];

    return cell;
}

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

- (void)setupImage:(UIImage*)thumbnail {

      self.cellImage = thumbnail;

}
person Warren Burton    schedule 16.08.2012
comment
Привет, Уоррен, я попробовал немного поиграть с этим, но ничего не получилось. Я работаю в основном потоке (я никогда не переключаюсь в фоновый режим), так что это не так. Я загрузил образец проекта barebone-комплекта, будьте любезны уделить несколько минут вашего дня, чтобы просмотреть его, я был бы бесконечно благодарен. - person ; 16.08.2012
comment
Настоящая проблема здесь в том, что то, что у вас здесь, никогда не будет работать. Вы никогда не должны вызывать cellForRowAtIndexPath: и ожидать, что получите ячейку, которая отображается в данный момент. См. РЕДАКТИРОВАТЬ - person Warren Burton; 17.08.2012

Вы можете использовать отложенную загрузку Apple TableView. У них есть примеры кодов, которые загружают изображения асинхронно. См. Ссылку ниже

Apple LazyTableImages

На вашем конце в классе AsynchronousImages вы можете добавить атрибут NSIndexPath, и делегат в AsynchronousImages должен быть изменен. См. Код ниже

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil)
    {
        cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    cell.cellName = [[array valueForKey:@"name"] objectAtIndex:indexPath.row];
    cell.cellImage = [UIImage imageNamed:@"placeholder.png"]; // add a placeholder    

    NSString *imageURL = [[array valueForKey:@"imageLink"] objectAtIndex:indexPath.row];
    NSURL *theURL = [NSURL URLWithString:imageURL];


    AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
    asynchronousImageLoader.indexPath = indexPath;
    [asynchronousImageLoader loadImageFromURL:theURL];
    return cell;
}

//Delegate should be
- (void)setupImage:(UIImage*)thumbnail index:(NSIndexPath*) indePath {
    TableCellLayout *cell = (TableCellLayout *) [tableView cellForRowAtIndexPath:indexPath];
    if(cell) {
      cell.cellImage = thumbnail;
      [cell setNeedsDisplay];
    }
}
person DLende    schedule 16.08.2012
comment
Я знаю пример Apple, но у меня сейчас все работает как есть. Я действительно думаю, что это просто вопрос возможности заставить видимые ячейки перерисовываться после завершения загрузки изображений. У вас есть идеи, как этого добиться? Спасибо за ответ! - person ; 16.08.2012
comment
но ваш код сложен, так как при триггере делегата setupImage вы не можете получить нужную строку ячейки для отображения изображения. Вы можете увидеть пример кода Apple, чтобы понять, как они это сделали. - person DLende; 16.08.2012
comment
Вы не released (или autoreleased) asynchronousImageLoader. - person thesummersign; 13.12.2012

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

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

person justin    schedule 16.08.2012
comment
Привет, Джастин, спасибо за это. В конце концов помог мне указать правильное направление! - person ; 19.08.2012

Используйте AsyncImageView вместо uiimageview

person Lithu T.V    schedule 16.08.2012
comment
Литу, я использую AsyncImageView. Изображения загружаются, но не отображаются пользователю до тех пор, пока видимые ячейки не будут перемещены с экрана (а затем снова включены). Я предполагаю, что это проблема принудительной перерисовки ячейки вручную. Мысли? - person ; 16.08.2012
comment
chk образец учебника, поставляемый с источником ... он хорошо объясняет, как заставить его работать правильно. в методе tableview cellforRow, установите свойство imageurl переменной asynchimage ... это отлично работает для меня ... надеюсь, что это поможет - person Lithu T.V; 16.08.2012
comment
Класс tht предоставляет не только категорию, но и подкласс asyncimageview для uiimageview - person Lithu T.V; 16.08.2012
comment
если вы можете сохранить данные, вы можете загрузить их в методе customcell drawrect, я думаю - person Lithu T.V; 16.08.2012

Вы можете указать своему tableView _1 _... или немного более точную перезагрузку:

  [self.tableView reloadRowsAtIndexPaths:self.tableView.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationNone];

Отредактируйте, чтобы добавить (после просмотра источника OP):

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

Лучше вместо этого заменить свойство изображения CellLayout на свойство UIImageView. (Обратите внимание, что UIImageView в любом случае более эффективен при рисовании, чем image drawAtPoint).

So:

  1. Измените свой класс CellLayout, чтобы использовать свойство UIImageView вместо UIImage
  2. Измените свой cellForRowAtIndexPath, чтобы установить AsyncImageView как свойство непосредственно в ячейке.

Если вы хотите поддерживать заполнители, их следует добавить в ваш класс AsyncImageView, чтобы он знал, что отображать при загрузке содержимого.

cellForRowAtIndexPath:

NSURL *theURL = [NSURL URLWithString:imageURL];

AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];

[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;

return cell;

CellLayout.h

@property (nonatomic, strong) UIImageView *cellImageView;

CellLayout.m

- (void)setCellImageView:(UIImageView *)s
{
    [cellImageView removeFromSuperview];
    cellImageView = s;
    [self addSubview:cellImageView];
}
person ikuramedia    schedule 16.08.2012
comment
Хм, я просмотрел все, что изложено в ответах, и не смог исправить это. Я знаю, что только начинаю, так что, наверное, упускаю что-то глупое. Я собрал образец проекта. Вы, вероятно, заняты, но если у вас есть несколько свободных минут, я был бы очень благодарен, если бы вы помогли раскрыть эту тайну! - person ; 16.08.2012