2013-03-27 2 views
30

Я загружаю изображения для своего uitableview асинхронно с помощью GCD, но возникает проблема - когда прокрутка изображений мерцает и меняется все время. Я попытался установить изображение на ноль с каждой ячейкой, но это мало помогает. При быстром прокрутке все изображения ошибочны. Что я могу сделать по этому поводу? Вот мой метод для клеток:Асинхронная загрузка изображений для UITableView с GCD

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

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 

       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 

       UIImage* image = [[UIImage alloc] initWithData:imageData]; 

       dispatch_async(dispatch_get_main_queue(), ^{ 
        cell.imageView.image = image; 
        [cell setNeedsLayout]; 
        }); 
      }); 

    cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 
    return cell; 
} 

ответ

89

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

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

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

Edit: Вот простой способ проверить, используя tag свойства ячейки:

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

    cell.tag = indexPath.row; 
    NSDictionary *parsedData = self.loader.parsedData[indexPath.row]; 
    if (parsedData) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
     dispatch_async(queue, ^(void) { 

      NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]]; 

      UIImage* image = [[UIImage alloc] initWithData:imageData]; 
      if (image) { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        if (cell.tag == indexPath.row) { 
         cell.imageView.image = image; 
         [cell setNeedsLayout]; 
        } 
       }); 
      } 
     }); 

     cell.textLabel.text = parsedData[@"id"]; 
    } 
    return cell; 
} 
+0

@SeamusCampbell, не может 'indexPath.row' быть одинаковыми для повторного использования ячейки, если он находится в другом разделе? В этом случае проверка тега будет проходить ненадлежащим образом. –

+0

Да, приведенный выше код предполагает таблицу из одного раздела. –

+0

@SeamusCampbell, любые мысли для таблицы с несколькими разделами :) –

7

Дело в том, что вы не в полной мере понять концепцию повторного использования клеток. Это не очень хорошо согласуется с асинхронными загрузками.

Блок

^{ 
    cell.imageView.image = image; 
    [cell setNeedsLayout]; 
} 

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

К тому времени, когда блок выполнен, ячейка по-прежнему указывает на одну из существующих ячеек. Но вполне вероятно, что пользователь продолжил прокрутку. Объект ячейки повторно использовался в то же время, и изображение связано с ячейкой старой, которая повторно используется и назначается и отображается. Вскоре после этого правильное изображение загружается, назначается и отображается, если пользователь не прокручивается дальше. И так далее, и так далее.

Вы должны искать более разумный способ сделать это. Есть много турориалов. Google для ленивой загрузки изображений.

6

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

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

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 
       // You may want to cache this explicitly instead of reloading every time. 
       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 
       UIImage* image = [[UIImage alloc] initWithData:imageData]; 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        // Capture the indexPath variable, not the cell variable, and use that 
        UITableViewCell *blockCell = [tableView cellForRowAtIndexPath:indexPath]; 
        blockCell.imageView.image = image; 
        [blockCell setNeedsLayout]; 
       }); 
      }); 
     cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 

    return cell; 
} 
+0

Просто примечание, это не работает с очень быстрой прокруткой. По-прежнему можно увидеть изображение, добавленное в неправильную ячейку. Если вы вызываете '[tableView cellForRowAtIndexPath: indexPath]' непосредственно перед повторной использованием ячейки, вызов 'blockCell.imageView.image = image' может быть вызван для ячейки после ее повторного использования. –

+0

@AndrewRobinson, если он не работает с быстрой прокруткой, то он не работает. Но я не видел, как этот подход терпит неудачу в том, как вы сами описываете, - не могли бы вы рассказать о том, как вы определили, что ячейка повторно использовалась для другого указательного пути при выполнении блока? –

+0

@AndrewRobinson, если бы мог опубликовать образец проекта на github или что-то, в чем я мог бы сыграть, я тоже хотел бы исследовать; не смотря на ваш проект, хотя, к сожалению, у меня нет проницательности. –

1

Вы думаете об использовании SDWebImage? Он также загружает изображение из заданного URL-адреса асинхронно. Я не использовал всю библиотеку (импортировал только UIImageView + WebCache.h). После импорта, все, что вам нужно сделать, это вызвать метод как таковой:

[UIImageXYZ sd_setImageWithURL:["url you're retrieving from"] placeholderImage:[UIImage imageNamed:@"defaultProfile.jpeg"]]; 

Это может быть излишним, если вы используете AFNetworking 2,0, но он работал для меня.

Here is the link to the github if you want to give it a try

3

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

#import <UIKit/UIKit.h> 

@interface MyCustomCell : UITableViewCell 

@property (nonatomic, strong) NSURLSessionDataTask *imageDownloadTask; 
@property (nonatomic, weak) IBOutlet UIImageView *myImageView; 
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *activityIndicator; 

@end 

Теперь в вашем TableViewController, объявить два свойства для NSURLSessionConfiguration и NSURLSession и инициализировать их в ViewDidLoad:

@interface MyTableViewController() 

@property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; 
@property (nonatomic, strong) NSURLSession *session; 
. 
. 
. 
@end 

@implementation TimesVC 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 
    _session = [NSURLSession sessionWithConfiguration:_sessionConfig]; 
} 

. 
. 
. 

Давайте supose, что ваш источник данных является массив из NSMutableDictionary (или NSManagedObjectContext). Вы можете легко загрузить изображение для каждой ячейки с кешированием, например:

. 
. 
. 
- (MyCustomCell *)tableView:(UITableView *)tableView 
    cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 

    if (!cell) 
    { 
     cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault 
           reuseIdentifier:@"cell"]; 
    } 

    NSMutableDictionary *myDictionary = [_myArrayDataSource objectAtIndex:indexPath.row];  

    if (cell.imageDownloadTask) 
    { 
     [cell.imageDownloadTask cancel]; 
    } 

    [cell.activityIndicator startAnimating]; 
    cell.myImageView.image = nil; 

    if (![myDictionary valueForKey:@"image"]) 
    { 
     NSString *urlString = [myDictionary valueForKey:@"imageURL"]; 
     NSURL *imageURL = [NSURL URLWithString:urlString]; 
     if (imageURL) 
     { 
      cell.imageDownloadTask = [_session dataTaskWithURL:imageURL 
       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
      { 
       if (error) 
       { 
        NSLog(@"ERROR: %@", error); 
       } 
       else 
       { 
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 

        if (httpResponse.statusCode == 200) 
        { 
         UIImage *image = [UIImage imageWithData:data]; 

         dispatch_async(dispatch_get_main_queue(), ^{ 
          [myDictionary setValue:data forKey:@"image"]; 
          [cell.myImageView setImage:image]; 
          [cell.activityIndicator stopAnimating]; 
         }); 
        } 
        else 
        { 
         NSLog(@"Couldn't load image at URL: %@", imageURL); 
         NSLog(@"HTTP %d", httpResponse.statusCode); 
        } 
       } 
      }]; 

      [cell.imageDownloadTask resume]; 
     } 
    } 
    else 
    { 
     [cell.myImageView setImage:[UIImage imageWithData:[myDictionary valueForKey:@"image"]]]; 
     [cell.activityIndicator stopAnimating]; 
    } 

    return cell; 
} 

Надеюсь, это поможет некоторым разработчикам! Cheers.

КРЕДИТЫ: Table View Images in iOS 7

2

Не изобретать колесо, просто использовать эту пару удивительных стручков:

https://github.com/rs/SDWebImage

https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage

просто:

[self.eventImage setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [SchemeConfiguration APIEndpoint] , _event.imageUrl]] 
          placeholderImage:nil 
           completed:^(UIImage *image, 
              NSError *error, 
              SDImageCacheType cacheType, 
              NSURL *imageURL) 
    { 
     event.image = UIImagePNGRepresentation(image); 
    } usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 
+0

Это было ограничение интервью, чтобы реализовать это без сторонних библиотек. – Dvole

1

Кроме того, nswer of Seamus Campbell вы также должны знать, что когда-нибудь это не сработает. В этом случае мы должны перезагрузить конкретную ячейку. Так

if (image) { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
      if (cell.tag == indexPath.row) { 
       cell.imageView.image = image; 
       [cell setNeedsLayout]; 
      } 
     }); 
} 

должен быть изменен в,

if (image) { 
     dispatch_async(dispatch_get_main_queue(), ^{ 
       if (cell.tag == indexPath.row) { 
        cell.imageView.image = image; 
        self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None) 
       } 
      }); 
    } 
Смежные вопросы