Cargando una imagen en UIImage de forma asíncrona

Estoy desarrollando una aplicación iOS 4 con iOS 5.0 SDK y XCode 4.2.

Tengo que mostrar algunos blogs de publicaciones en una UITableView. Cuando he recuperado todos los datos del servicio web, utilizo este método para crear una 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; } 

Pero esta línea:

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

es muy lento (entry.photo tiene una url http).

¿Hay alguna manera de cargar esa imagen de forma asíncrona? Creo que es difícil porque se llama tableView: cellForRowAtIndexPath muy a menudo.

Eche un vistazo a SDWebImage:

https://github.com/rs/SDWebImage

Es un fantástico conjunto de clases que manejan todo por ti.

Tim

Escribí una clase personalizada para hacer esto, usando bloques y GCD:

WebImageOperations.h

 #import  @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  @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 

Y en tu

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

Es muy rápido y cargará las imágenes afectando la UI o la velocidad de desplazamiento de TableView.

*** Nota: este ejemplo supone que se está utilizando ARC. De lo contrario, deberá gestionar sus propios lanzamientos en los objetos)

En iOS 6 y posteriores, dispatch_get_current_queue proporciona advertencias de desactivación.

Aquí hay una alternativa que es una síntesis de la respuesta @ElJay anterior y el artículo de @khanlou aquí .

Crea una categoría en 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 

Rápido:

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

Uso:

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

Considerando el caso de falla.

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

Sí, es relativamente fácil. La idea es algo así como:

  1. Crea una matriz mutable para contener tus imágenes
  2. Complete la matriz con marcadores de posición si lo desea (o NSNull si no lo hace)
  3. Crea un método para recuperar tus imágenes de forma asincrónica en el fondo
  4. Cuando haya llegado una imagen, cambie su marcador de posición con la imagen real y haga un [self.tableView reloadData]

He probado este enfoque muchas veces y obtengo excelentes resultados. Si necesita ayuda / ejemplos con la parte asincrónica (recomiendo usar gcd para eso) avíseme.

puede hacerlo perfectamente si utiliza el código de ejemplo proporcionado por Apple para este fin: el código de muestra: Lazy Image

Basta con mirar el delegado rowforcell y agregar archivos icondownloader a su proyecto.

El único cambio que tiene que hacer es cambiar el objeto apprecord con su objeto.

Si bien SDWebImage y otras soluciones de terceros pueden ser excelentes, si no está interesado en utilizar API de terceros, también puede desarrollar su propia solución.

Consulte este tutorial sobre la carga diferida, que también trata sobre cómo debe modelar sus datos dentro de la vista de tabla.

Swift 4 | Carga asincrónica de la imagen

Crear una nueva clase llamada ImageLoader.swift

 import UIKit class ImageLoader { var cache = NSCache() 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() } } 

Para usar en su clase ViewController:

 ImageLoader.sharedInstance.imageForUrl(urlString: "http://sofes.miximages.com/ios/apple-logo-rob-janoff-01.jpg", completionHandler: { (image, url) in if image != nil { self.imageView.image = image } }) 

Probablemente deba subclasificar su UIImageView. Hace poco hice un proyecto simple para explicar esta tarea en particular: carga de imágenes asíncrona en segundo plano, eche un vistazo a mi proyecto en GitHub. Específicamente, mira la clase KDImageView .