Загрузка изображения в UIImage асинхронно - PullRequest
35 голосов
/ 20 марта 2012

Я занимаюсь разработкой приложения для iOS 4 с iOS 5.0 SDK и XCode 4.2.

Мне нужно показать некоторые пост-блоги в UITableView.Получив все данные веб-службы, я использую этот метод для создания UITableViewCell:

- (BlogTableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* cellIdentifier = @"BlogCell";

    BlogTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (cell == nil)
    {
        NSArray* topLevelObjects =  [[NSBundle mainBundle] loadNibNamed:@"BlogTableViewCell" owner:nil options:nil];

        for(id currentObject in topLevelObjects)
        {
            if ([currentObject isKindOfClass:[BlogTableViewCell class]])
            {
                cell = (BlogTableViewCell *)currentObject;
                break;
            }
        }
    }

    BlogEntry* entry = [blogEntries objectAtIndex:indexPath.row];

    cell.title.text = entry.title;
    cell.text.text = entry.text;
    cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];

    return cell;
}

Но эта строка:

cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];

очень медленная (entry.photo имеетhttp url).

Есть ли способ загрузить это изображение асинхронно?Я думаю, что это сложно, потому что tableView: cellForRowAtIndexPath вызывается очень часто.

Ответы [ 10 ]

81 голосов
/ 20 марта 2012

Я написал собственный класс для этого, используя блоки и GCD:

WebImageOperations.h

#import <Foundation/Foundation.h>

@interface WebImageOperations : NSObject {
}

// This takes in a string and imagedata object and returns imagedata processed on a background thread
+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
@end

WebImageOperations.m

#import "WebImageOperations.h"
#import <QuartzCore/QuartzCore.h>

@implementation WebImageOperations


+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_current_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
    dispatch_release(downloadQueue);
}

@end

И вваш ViewController

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

    // Pass along the URL to the image (or change it if you are loading there locally)
    [WebImageOperations processImageDataWithURLString:entry.photo andBlock:^(NSData *imageData) {
    if (self.view.window) {
        UIImage *image = [UIImage imageWithData:imageData];

        cell.photo.image = image;
    }

    }];
}

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

*** Примечание. В этом примере предполагается, что используется ARC.Если нет, вам нужно будет управлять своими собственными выпусками на объектах)

52 голосов
/ 17 сентября 2013

В iOS 6 и более поздних версиях dispatch_get_current_queue выдает предупреждения об устаревании.

Вот альтернатива, которая представляет собой синтез ответа @ElJay выше и статьи @khanlou здесь .

Создание категории на UIImage:

UIImage + Helpers.h

@interface UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback;

@end

UIImage + Helpers.m

#import "UIImage+Helpers.h"

@implementation UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithData:imageData];
            callback(image);
        });
    });
}

@end
28 голосов
/ 20 марта 2012

Посмотрите на SDWebImage:

https://github.com/rs/SDWebImage

Это фантастический набор классов, которые справятся со всем за вас.

Тим

6 голосов
/ 02 декабря 2014

Swift:

extension UIImage {

    // Loads image asynchronously
    class func loadFromURL(url: NSURL, callback: (UIImage)->()) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {

            let imageData = NSData(contentsOfURL: url)
            if let data = imageData {
                dispatch_async(dispatch_get_main_queue(), {
                    if let image = UIImage(data: data) {
                        callback(image)
                    }
                })
            }
        })
    }
}

Использование:

// First of all remove the old image (required for images in cells)
imageView.image = nil 

// Load image and apply to the view
UIImage.loadFromURL("http://...", callback: { (image: UIImage) -> () in
     self.imageView.image = image
})
3 голосов
/ 13 декабря 2017

Swift 4 | Асинхронная загрузка изображения

Создайте новый класс с именем ImageLoader.swift

import UIKit

class ImageLoader {

var cache = NSCache<AnyObject, AnyObject>()

class var sharedInstance : ImageLoader {
    struct Static {
        static let instance : ImageLoader = ImageLoader()
    }
    return Static.instance
}

func imageForUrl(urlString: String, completionHandler:@escaping (_ image: UIImage?, _ url: String) -> ()) {
        let data: NSData? = self.cache.object(forKey: urlString as AnyObject) as? NSData

        if let imageData = data {
            let image = UIImage(data: imageData as Data)
            DispatchQueue.main.async {
                completionHandler(image, urlString)
            }
            return
        }

    let downloadTask: URLSessionDataTask = URLSession.shared.dataTask(with: URL.init(string: urlString)!) { (data, response, error) in
        if error == nil {
            if data != nil {
                let image = UIImage.init(data: data!)
                self.cache.setObject(data! as AnyObject, forKey: urlString as AnyObject)
                DispatchQueue.main.async {
                    completionHandler(image, urlString)
                }
            }
        } else {
            completionHandler(nil, urlString)
        }
    }
    downloadTask.resume()
    }
}

Для использования в вашем классе ViewController:

ImageLoader.sharedInstance.imageForUrl(urlString: "https://www.logodesignlove.com/images/classic/apple-logo-rob-janoff-01.jpg", completionHandler: { (image, url) in
                if image != nil {
                    self.imageView.image = image
                }
            })
3 голосов
/ 09 сентября 2015

Рассмотрение дела об отказе.

- (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        NSError * error = nil;
        NSData * imageData = [NSData dataWithContentsOfURL:url options:0 error:&error];
        if (error)
            callback(nil);

        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithData:imageData];
            callback(image);
        });
    });
}
2 голосов
/ 20 марта 2012

Да, это относительно легко.Идея примерно такая:

  1. Создать изменяемый массив для хранения ваших изображений
  2. Заполните массив заполнителями, если хотите (или объектами NSNull, если вы этого не сделаете)
  3. Создайте метод для асинхронной выборки ваших изображений в фоновом режиме.
  4. Когда изображение получено, поменяйте местами местозаполнитель с реальным изображением и выполните [self.tableView reloadData]

У меня естьпроверил этот подход много раз и дает отличные результаты.Если вам нужна помощь / примеры с асинхронной частью (я рекомендую использовать gcd для этого), дайте мне знать.

1 голос
/ 16 января 2016

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

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

1 голос
/ 05 сентября 2013

Вы можете легко сделать это отлично, если вы используете для этого пример кода, предоставленного Apple: пример кода: Ленивое изображение

Просто посмотрите на rowforcell делегата и добавьте файлы icondownloaderк вашему проекту.

Единственное изменение, которое вам нужно сделать, - это изменить объект адорда вашим объектом.

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

Возможно, вам придется создать подкласс вашего UIImageView. Недавно я сделал простой проект для объяснения этой конкретной задачи - фоновую асинхронную загрузку изображений - взгляните на мой проект на GitHub. В частности, посмотрите на KDImageView class.

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