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:
[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 .