Подтвердить что ты не робот

Загрузка изображения в UIImage асинхронно

Я разрабатываю приложение 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-адрес).

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

4b9b3361

Ответ 1

Взгляните на SDWebImage:

https://github.com/rs/SDWebImage

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

Тим

Ответ 2

Я написал собственный класс, чтобы сделать это, используя блоки и 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

И в вашем

    - (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. Если нет, вам нужно будет управлять своими собственными версиями на объектах)

Ответ 3

В 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

Ответ 4

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
})

Ответ 5

Рассмотрение случая сбоя.

- (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);
        });
    });
}

Ответ 6

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

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

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

Ответ 7

вы можете легко сделать это отлично, если вы используете пример кода, предоставленный Apple для этой цели: пример кода: Lazy Image

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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
                }
            })