2012-09-29 19 views
12

Estoy cargando imágenes en miniatura de publicaciones de blog de manera asincrónica en mi UITableView.Las imágenes descargadas de forma asíncrona solo aparecen en UITableView después de tocar o deslizar

El problema que tengo es que las imágenes solo aparecen si toco la celda O si me desplazo hacia abajo.

Cuando toco la celda, la imagen aparece a la izquierda, empujando el Título y el Subtítulo a la derecha.

Cuando me desplazo hacia abajo, las imágenes aparecen donde deberían en las celdas a medida que se revelan.

Aquí está mi código (estoy usando AFNetworking):

#import "UIImageView+AFNetworking.h" 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
    return posts.count; 
} 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"cell"; 

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 

    if (cell == nil) { 
     cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; 
    } 

    NSDictionary *post   = [posts objectAtIndex:indexPath.row]; 
    NSString  *postpictureUrl = [post objectForKey:@"picture"]; 

    [cell.imageView setImageWithURL:[NSURL URLWithString:postpictureUrl]]; 

    cell.textLabel.text  = [post objectForKey:@"post_text"]; 
    cell.detailTextLabel.text = [post objectForKey:@"post_author_name"]; 
    return cell; 
} 

que estoy viendo esto en el simulador de iPhone 6.0, 4.5 XCode, OSX MtLion.

¿Alguna idea de por qué las imágenes no se dibujan en la pantalla inicial?

+0

@carlveazy ¿hay alguna forma de solucionar este problema de tamaño utilizando AFNetworking? – pepe

+1

No estoy seguro con AFNetworking, pero probablemente tendrá que implementar la devolución de llamada exitosa desde su categoría 'UIImageView' y asegurarse de que la celda reciba' setNeedsLayout' una vez que la imagen se actualice. Además, tendrás que asegurarte de que la reutilización de las células no se interponga aquí, lo que puede ser complicado, pero supongo que es un problema aparte. –

+0

Parece que hay una opción aquí, pero no estoy seguro si esto resuelve lo que estás sugiriendo - http://afnetworking.github.com/AFNetworking/Categories/UIImageView+AFNetworking.html – pepe

Respuesta

12

Lo que quiere estar al tanto de cuando se mezclan desincronizado y tablas es que el asynch termina en un momento desconocido en el futuro, posiblemente después de la célula se desplaza instante, sacado, reutilizados, etc.

Además, la imagen que se extrae de la web se pierde si esa celda se desplaza. No estoy seguro si AFNetworking almacena en caché, pero podría ser mejor no asumirlo. He aquí una solución utilizando una red nativa:

// ... 
NSDictionary *post   = [posts objectAtIndex:indexPath.row]; 
NSString  *postpictureUrl = [post objectForKey:@"picture"]; 

// find a place in your model, or add one, to cache an actual downloaded image 
UIImage  *postImage  = [post objectForKey:@"picture_image"]; 

if (postImage) { 
    cell.imageView.image = postImage; // this is the best scenario: cached image 
} else { 
    // notice how we don't pass the cell - we don't trust its value past this turn of the run loop 
    [self asynchLoad:postpictureUrl forIndexPath:indexPath]; 
    cell.imageView.image = [UIImage imageNamed:@"default"]; 
} 
// ... 

Ahora, una carga asynch sin sentido y sin ninguna ayuda tercera parte

- (void)asynchLoad:(NSString *)urlString forIndexPath:(NSIndexPath *)indexPath { 

    NSURL *url = [NSURL urlWithString:urlString]; 
    NSURLRequest *request = [NSURLRequest requestWithURL:url]; 

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { 
     if (!error) { 

      // create the image 
      UIImage *image = [UIImage imageWithData:data]; 

      // cache the image 
      NSDictionary *post = [posts objectAtIndex:indexPath.row]; 
      [post setObject:image forKey:@"picture_image"]; 

      // important part - we make no assumption about the state of the table at this point 
      // find out if our original index path is visible, then update it, taking 
      // advantage of the cached image (and a bonus option row animation) 

      NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows]; 
      if ([visiblePaths containsObject:indexPath]) { 
       NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; 
       [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation: UITableViewRowAnimationFade]; 
       // because we cached the image, cellForRow... will see it and run fast 
      } 
     } 
    }]; 
} 

Para que esto funcione, los mensajes deben ser creados como NSMutableDictionary ...

// someplace in your code you add a post to the posts array. do this instead. 

NSDictionary *postData = // however you get a new post 
[posts addObject:[NSMutableDictionary dictionaryWithDictionary:postData]]; 

Alternativamente, si es difícil cambiar el modelo de mensajes directamente, puede configurar otra estructura para almacenar en caché las imágenes descargadas. Un diccionario mutable introducido por las cadenas de URL es una buena estructura de empleo:

@property (nonatomic,strong) NSMutableDictionary *imageCache; 
@synthesize imageCache=_imageCache; 

// lazy init on the getter... 

- (NSMutableDictionary *)imageCache { 
    if (!_imageCache) { 
     _imageCache = [NSMutableDictionary dictionary]; 
    } 
    return _imageCache; 
} 

Ahora, cuando la configuración de la célula, a ver si hay una imagen almacenada en caché mediante la comprobación de la memoria caché ...

// change to the cellForRowAtIndexPath method 
NSString *postpictureUrl = [post objectForKey:@"picture"]; 
UIImage *postImage = [self.imageCache valueForKey:postpictureUrl]; 

Y una vez que se descarga una imagen, en caché ...

// change to the asynchLoad: method I suggested 
UIImage *image = [UIImage imageWithData:data]; 
[self.imageCache setValue:image forKey:urlString]; 
+0

Esta es una muy buena respuesta. Hacer esto en una base por índice es absolutamente la forma correcta. Además, gracias por la sugerencia de volver a cargar las filas, eso realmente tiene más sentido que 'setNeedsLayout' y creo que lo usaré a partir de ahora. No he intentado esto, pero una variación puede implicar el uso de 'NSCache' con las rutas de índice como las claves mismas. –

+0

@danh esto suena como una buena idea - Estoy obteniendo un error de análisis en esta línea '[post setObject: image forKey: @" picture_image "];' --- 'No visible @interface para 'NSDictionary' declara el selector 'setObject: forKey:' ' – pepe

+0

La publicación es un diccionario. Para cambiarlo, debe ser mutable. Podrías hacerlos mutables cuando los crees o cambiarlos sobre la marcha. Agregaré un código para ilustrar el último. – danh

5

el problema se resuelve poniendo un marcador de posición en esta línea

... 
[cell.imageView setImageWithURL:[NSURL URLWithString:postpictureUrl] placeholderImage:[UIImage imageNamed:@"default"]]; 
.... 

El marcador de posición necesita tener relación de dimensión similar a la imagen en miniatura para evitar la distorsión.

+2

Específicamente la razón para esto es que si la propiedad 'imageView' de' UITableViewCell' es nula, durante 'layoutSubviews 'cambia el tamaño de' imageView' a {0,0}. Establecer un marcador de posición evita este cambio de tamaño. Si no desea tener un marcador de posición, debe llamar a 'setNeedsLayout' en el momento en que configure' cell.imageView.image'. EDITAR: danh saca un buen punto que puede volver a cargar la celda para forzar el tamaño correcto. –

1

esta es mi solución, usando una categoría para UIImageView.
NOTA: dado que realizamos self.image = nil en la primera línea, debe establecer una imagen de marcador de posición para la celda.ImageView después de llamar a este método.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
... 
    [cell.imageView loadImageForURLString:row.imageUrl]; 
    cell.imageView.image = tempImage; 
... 
} 

y la categoría:

#import "UIImageView+AsyncLoad.h" 

@implementation UIImageView (AsyncLoad) 

- (void)loadImageForURLString:(NSString *)imageUrl 
{ 
    self.image = nil; 

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; 
    NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]]; 
    [NSURLConnection sendAsynchronousRequest:request 
             queue:[NSOperationQueue mainQueue] 
          completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) 
    { 
     [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; 
     if (data && self.window) { 
      self.image = [UIImage imageWithData:data]; 
     } 
    }]; 
} 

@end 
+1

Sería aconsejable eliminar el yo desde el interior del bloque. Utilice una referencia débil a la instancia de imageviews en su lugar. como __weak UIImageView * wself = self; if (datos ** wself.window) {wself.image = [UIImage imageWithData: data];} –

0

Estoy bastante tarde a la fiesta, pero si cavar un poco más profundo en el UIImageView+AFNetworking docs se encuentra el método – setImageWithURLRequest:placeholderImage:success:failure: que se puede utilizar para recargar la celda cuando la imagen está disponible:

NSURLRequest *urlRequest = [NSURLRequest requestWithURL: [NSURL URLWithString: imageURL]]; 
__weak UITableViewCell *weakCell = cell; 

[cell.imageView setImageWithURLRequest: urlRequest 
         placeholderImage: nil 
           success: ^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { 

            __strong UITableViewCell *strongCell = weakCell; 
            strongCell.imageView.image = image; 

            [tableView reloadRowsAtIndexPaths: @[indexPath] 
                withRowAnimation: UITableViewRowAnimationNone]; 

           } failure: NULL]; 
2

Me rasqué la cabeza por mucho tiempo y finalmente lo descubrí.

Mi error fue que estaba configurando la imagen en cell.imageView cuando debería configurar mi salida real cell.eventImageView. Estaba jugando con la vista de imagen genérica proporcionada en UITableViewCell. Espero que ayude a alguien.

+0

hombre que ha guardado mis miserables colillas. ¡Gracias! – JadeSync

Cuestiones relacionadas