ios - ¿Cómo cargar de forma asíncrona una imagen en un UIImageView?
objective-c grand-central-dispatch (10)
Tengo un UIView con una subvista UIImageView. Necesito cargar una imagen en UIImageView sin bloquear la interfaz de usuario. La llamada de bloqueo parece ser: UIImage imageNamed:
Esto es lo que pensé que resolvió este problema:
-(void)updateImageViewContent {
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage:img];
});
});
}
La imagen es pequeña (150x100).
Sin embargo, la interfaz de usuario todavía está bloqueada al cargar la imagen. Qué me estoy perdiendo ?
Aquí hay un pequeño ejemplo de código que muestra este comportamiento:
Cree una nueva clase basada en UIImageView, establezca su interacción de usuario en SÍ, agregue dos instancias en una UIView e implemente su método touchesBegan de la siguiente manera:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.tag == 1) {
self.backgroundColor= [UIColor redColor];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
});
[UIView animateWithDuration:0.25 animations:
^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
}
}
Asigne la etiqueta 1 a una de estas vistas de imagen.
¿Qué sucede exactamente cuando toca las dos vistas casi simultáneamente, comenzando con la vista que carga una imagen? ¿Se bloquea la interfaz de usuario porque está esperando [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
regresar ? Si es así, ¿cómo puedo hacer esto de forma asíncrona?
Aquí hay un proyecto en github con código ipmcc
Presione prolongadamente y luego arrastre para dibujar un rectángulo alrededor de los cuadrados negros. Según entiendo su respuesta, en teoría, el rectángulo de selección de blanco no debe bloquearse la primera vez que se carga la imagen, pero en realidad sí lo está.
Se incluyen dos imágenes en el proyecto (una pequeña: woodenTile.jpg y una más grande: bois.jpg). El resultado es el mismo con ambos.
Formato de imagen
Realmente no entiendo cómo se relaciona esto con el problema que todavía tengo con la interfaz de usuario bloqueada mientras se carga la imagen por primera vez, pero las imágenes PNG se decodifican sin bloquear la interfaz de usuario, mientras que las imágenes JPG bloquean la interfaz de usuario.
Cronología de los acontecimientos.
El bloqueo de la interfaz de usuario comienza aquí.
.. y termina aquí.
Solución de red
NSURL * url = [ [NSBundle mainBundle]URLForResource:@"bois" withExtension:@"jpg"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[self.imageView setImageWithURLRequest:request
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(@"success: %@", NSStringFromCGSize([image size]));
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(@"failure: %@", response);
}];
// this code works. Used to test that url is valid. But it''s blocking the UI as expected.
if (false)
if (url) {
[self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }
La mayoría de las veces, registra: success: {512, 512}
También registra ocasionalmente: success: {0, 0}
Y a veces: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...
Pero la imagen nunca se cambia.
¿Por qué no utiliza una biblioteca de terceros como AsyncImageView ? Con esto, todo lo que tiene que hacer es declarar su objeto AsyncImageView y pasar la url o la imagen que desea cargar. Se mostrará un indicador de actividad durante la carga de la imagen y nada bloqueará la interfaz de usuario.
- (void) touchesBegan: se llama en el hilo principal. Al llamar a dispatch_async (dispatch_get_main_queue) acaba de poner el bloque en la cola. Este bloque será procesado por GCD cuando la cola esté lista (es decir, el sistema ha terminado con el procesamiento de sus toques). Es por eso que no puede ver su woodenTile cargado y asignado a self.image hasta que suelte el dedo y deje que GCD procese todos los bloques que se han puesto en cola en la cola principal.
Reemplazo:
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
});
por:
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
Debería resolver su problema ... al menos para el código que lo exhibe.
Considere utilizar SDWebImage : no solo descarga y almacena en caché la imagen en segundo plano, sino que también la carga y la procesa.
Lo he usado con buenos resultados en una vista de tabla que tenía imágenes grandes que tardaban en cargarse incluso después de descargarse.
Cuando está usando AFNetwork en una aplicación, no necesita usar ningún bloque para cargar la imagen porque AFNetwork le ofrece una solución. Como a continuación:
#import "UIImageView+AFNetworking.h"
Y
Use **setImageWithURL** function of AFNetwork....
Gracias
El problema es que UIImage
realidad no lee ni decodifica la imagen hasta la primera vez que se usa / dibuja. Para forzar que este trabajo ocurra en un hilo de fondo, debe usar / dibujar la imagen en el hilo de fondo antes de hacer el hilo principal -setImage:
Esto funcionó para mí:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
// Make a trivial (1x1) graphics context, and draw the image into it
UIGraphicsBeginImageContext(CGSizeMake(1,1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
UIGraphicsEndImageContext();
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage: img];
});
});
EDIT: La interfaz de usuario no está bloqueando. Lo has configurado específicamente para usar UILongPressGestureRecognizer
que espera, de manera predeterminada, medio segundo antes de hacer nada. El hilo principal todavía está procesando eventos, pero nada va a suceder hasta que GR se agote el tiempo. Si haces esto:
longpress.minimumPressDuration = 0.01;
... te darás cuenta de que se vuelve mucho más rápido. La carga de la imagen no es el problema aquí.
EDITAR 2: He mirado el código, publicado en github, ejecutándose en un iPad 2, y simplemente no tengo el problema que estás describiendo. De hecho, es bastante suave. Aquí hay una captura de pantalla de ejecutar el código en el instrumento CoreAnimation:
Como puede ver en el gráfico superior, el FPS va hasta ~ 60 FPS y permanece allí durante todo el gesto. En el gráfico inferior, puede ver el blip a unos 16 segundos, que es donde se carga la imagen por primera vez, pero puede ver que no hay una caída en la velocidad de cuadros. Solo por la inspección visual, veo que la capa de selección se cruza, y hay un pequeño retraso, pero observable, entre la primera intersección y la apariencia de la imagen. Por lo que puedo decir, el código de carga en segundo plano está haciendo su trabajo como se esperaba.
Desearía poder ayudarte más, pero no veo el problema.
Puede usar la biblioteca AFNetworking , en la cual al importar la categoría
"UIImageView + AFNetworking.m" y utilizando el método siguiente:
[YourImageView setImageWithURL:[NSURL URLWithString:@"http://image_to_download_from_serrver.jpg"]
placeholderImage:[UIImage imageNamed:@"static_local_image.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
//ON success perform
}
failure:NULL];
espero que esto ayude .
Tuve un problema muy similar con mi aplicación, donde tuve que descargar muchas imágenes y, junto con eso, mi interfaz de usuario se estaba actualizando continuamente. A continuación se muestra el enlace tutorial simple que resolvió mi problema:
Una forma en que lo he implementado es la siguiente: (Aunque no sé si es la mejor)
Al principio creo una cola usando Serial Asynchronous o en Parallel Queue
queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**
or
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
**
Lo que cada vez puede encontrar mejor para sus necesidades.
Después:
dispatch_async( queue, ^{
// Load UImage from URL
// by using ImageWithContentsOfUrl or
UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// Then to set the image it must be done on the main thread
dispatch_sync( dispatch_get_main_queue(), ^{
[page_cover setImage: imagename];
imagename = nil;
});
});
esta es la buena manera
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
[[self imageView] setImage:img];
});
}
https://github.com/nicklockwood/FXImageView
Esta es una vista de imagen que puede manejar la carga de fondo.
Uso
FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;
//show placeholder
imageView.processedImage = [UIImage imageNamed:@"placeholder.png"];
//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];
Para obtener una URL para su archivo, puede encontrar lo siguiente interesante:
Obtención de referencias / rutas de archivo de paquete en el inicio de la aplicación