He escrito dos formas de cargar imágenes asíncronamente dentro de mi celda UITableView. En ambos casos, la imagen se cargará bien, pero cuando desplazaré la tabla, las imágenes cambiarán varias veces hasta que el desplazamiento finalice y la imagen vuelva a la imagen correcta. No tengo idea de por qué está sucediendo esto.
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
@"http://myurl.com/getMovies.php"]];
[self performSelectorOnMainThread:@selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
}
-(void)fetchedData:(NSData *)data
{
NSError* error;
myJson = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error];
[_myTableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// Return the number of rows in the section.
// Usually the number of items in your array (the one that holds your list)
NSLog(@"myJson count: %d",[myJson count]);
return [myJson count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
dispatch_async(kBgQueue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.poster.image = [UIImage imageWithData:imgData];
});
});
return cell;
}
... ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
cell.poster.image = [UIImage imageWithData:data];
// do whatever you want with image
}
}];
return cell;
}
poster
? Es presumiblemente una vista de imagen en su celda personalizada, por lo que EXEC_BAD_ACCESS está haciendo perfectamente bien. Tiene razón en que no debe usar la celda como depósito de datos del modelo, pero no creo que eso sea lo que está haciendo. Simplemente le está dando a la celda personalizada lo que necesita para presentarse. Además, y este es un tema más sutil, me preocuparía almacenar una imagen, en sí misma, en su matriz de modelos que respalde su vista de tabla. Es mejor usar un mecanismo de almacenamiento en caché de imágenes y su objeto modelo debería recuperarse de ese caché.Respuestas:
Suponiendo que está buscando una solución táctica rápida, lo que debe hacer es asegurarse de que la imagen de la celda esté inicializada y también que la fila de la celda todavía esté visible, por ejemplo:
El código anterior aborda algunos problemas derivados del hecho de que la celda se reutiliza:
No está inicializando la imagen de la celda antes de iniciar la solicitud de fondo (lo que significa que la última imagen de la celda en cola aún estará visible mientras se descarga la nueva imagen). Asegúrese de
nil
laimage
propiedad de cualquier vista de imagen o de lo contrario verá el parpadeo de las imágenes.Un problema más sutil es que en una red realmente lenta, su solicitud asincrónica podría no finalizar antes de que la celda se desplace de la pantalla. Puede usar el
UITableView
métodocellForRowAtIndexPath:
(que no debe confundirse con elUITableViewDataSource
método con un nombre similartableView:cellForRowAtIndexPath:
) para ver si la celda de esa fila todavía está visible. Este método volveránil
si la celda no es visible.El problema es que la celda se ha desplazado cuando el método asincrónico se ha completado y, lo que es peor, la celda se ha reutilizado para otra fila de la tabla. Al verificar si la fila aún está visible, se asegurará de no actualizar accidentalmente la imagen con la imagen de una fila que desde entonces se ha desplazado fuera de la pantalla.
Algo no relacionado con la pregunta en cuestión, todavía me sentí obligado a actualizar esto para aprovechar las convenciones modernas y API, en particular:
Usar en
NSURLSession
lugar de enviar-[NSData contentsOfURL:]
a una cola en segundo plano;Use en
dequeueReusableCellWithIdentifier:forIndexPath:
lugar dedequeueReusableCellWithIdentifier:
(pero asegúrese de usar el prototipo de celda o la clase de registro o NIB para ese identificador); yUsé un nombre de clase que se ajusta a las convenciones de nomenclatura de Cocoa (es decir, comienza con la letra mayúscula).
Incluso con estas correcciones, hay problemas:
El código anterior no almacena en caché las imágenes descargadas. Eso significa que si desplaza una imagen fuera de la pantalla y nuevamente en la pantalla, la aplicación puede intentar recuperar la imagen nuevamente. Quizás tenga la suerte de que los encabezados de respuesta de su servidor permitirán el almacenamiento en caché bastante transparente ofrecido por
NSURLSession
yNSURLCache
, pero si no, realizará solicitudes innecesarias del servidor y ofrecerá una experiencia de usuario mucho más lenta.No estamos cancelando solicitudes de celdas que se desplazan fuera de la pantalla. Por lo tanto, si se desplaza rápidamente a la fila 100, la imagen de esa fila podría acumularse detrás de las solicitudes de las 99 filas anteriores que ya ni siquiera son visibles. Siempre debe asegurarse de priorizar las solicitudes de celdas visibles para la mejor experiencia de usuario.
La solución más simple que aborda estos problemas es usar una
UIImageView
categoría, como se proporciona con SDWebImage o AFNetworking . Si lo desea, puede escribir su propio código para tratar los problemas anteriores, pero es mucho trabajo, y lasUIImageView
categorías anteriores ya lo han hecho por usted.fuente
updateCell.poster.image = nil
tocell.poster.image = nil;
updateCell se llama antes de que se declare.AFNetworking
que definitivamente es el camino a seguir. Lo sabía pero era demasiado flojo para usarlo. Solo admiro cómo funciona el almacenamiento en caché con su simple línea de código.[imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
cellForRowAtIndexPath
resultado un parpadeo de las imágenes cuando me desplazo rápidamente" y le expliqué por qué sucedió eso y cómo solucionarlo. Pero seguí explicando por qué incluso eso era insuficiente, describí algunos problemas más profundos y discutí por qué sería mejor usar una de esas bibliotecas para manejar esto con más gracia (priorizar solicitudes de celdas visibles, almacenamiento en caché para evitar redes redundantes solicitudes, etc.). No tengo claro qué más esperaba en respuesta a la pregunta de "¿cómo detengo las imágenes parpadeantes en la vista de mi tabla"?/ * Lo hice de esta manera, y también lo probé * /
Paso 1 = Registre la clase de celda personalizada (en el caso de la celda prototipo en la tabla) o nib (en el caso de la punta personalizada para la celda personalizada) para una tabla como esta en el método viewDidLoad:
O
Paso 2 = Use el método "dequeueReusableCellWithIdentifier: forIndexPath:" de UITableView de esta manera (para esto, debe registrar la clase o punta):
fuente
Existen múltiples marcos que resuelven este problema. Sólo para nombrar unos pocos:
Rápido:
C objetivo:
fuente
SDWebImage
no resuelve este problema. Puede controlar cuándo se descargaSDWebImage
la imagen , pero asigne la imagenUIImageView
sin pedirle permiso para hacerlo. Básicamente, el problema de la pregunta aún no se resuelve con esta biblioteca.Swift 3
Escribo mi propia implementación ligera para el cargador de imágenes con NSCache. ¡Ninguna imagen celular parpadeando!
ImageCacheLoader.swift
Ejemplo de uso
fuente
Aquí está la versión rápida (usando el código C objetivo de @Nitesh Borad):
fuente
La mejor respuesta no es la forma correcta de hacer esto :(. En realidad, vinculó indexPath con el modelo, lo que no siempre es bueno. Imagine que se han agregado algunas filas durante la carga de la imagen. Ahora la celda para indexPath dada existe en la pantalla, pero la imagen ya no es correcto! La situación es poco probable y difícil de replicar, pero es posible.
¡Es mejor usar el enfoque MVVM, vincular la celda con viewModel en el controlador y cargar la imagen en viewModel (asignar la señal ReactiveCocoa con el método switchToLatest), luego suscribir esta señal y asignar la imagen a la celda! ;)
Debe recordar no abusar de MVVM. ¡Las vistas tienen que ser muy simples! ¡Mientras que ViewModels debería ser reutilizable! Es por eso que es muy importante vincular View (UITableViewCell) y ViewModel en el controlador.
fuente
UIImageView
solución de categoría que le aconsejo, no existe tal problema con respecto a las rutas de índice.En mi caso, no se debió al almacenamiento en caché de imágenes (Used SDWebImage). Fue debido a la falta de coincidencia de la etiqueta de la celda personalizada con indexPath.row.
En cellForRowAtIndexPath:
1) Asigne un valor de índice a su celda personalizada. Por ejemplo,
2) En el hilo principal, antes de asignar la imagen, verifique si la imagen pertenece a la celda correspondiente haciendo coincidirla con la etiqueta.
fuente
Gracias "Rob" ... Tuve el mismo problema con UICollectionView y su respuesta me ayudó a resolver mi problema. Aquí está mi código:
fuente
mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
nunca es nulo, por lo que esto no tiene ningún efecto.visibleCells
esa manera, pero sospecho que usar[collectionView cellForItemAtIndexPath:indexPath]
es más eficiente (y es por eso que haces esa llamada en primer lugar).updateCell
no es asínil
, pero luego no lo usa. Debe usarlo no solo para determinar si la celda de la vista de colección aún está visible, sino que debe usarlaupdateCell
dentro de este bloque, nocell
(lo que puede que ya no sea válido). Y obviamente, si es asínil
, no necesita hacer nada (porque esta celda no es visible).fuente
Creo que desea acelerar la carga de su celda en el momento de la carga de la imagen para la celda en segundo plano. Para eso hemos realizado los siguientes pasos:
La comprobación del archivo existe en el directorio del documento o no.
Si no, cargue la imagen por primera vez y guárdela en nuestro directorio de documentos del teléfono. Si no desea guardar la imagen en el teléfono, puede cargar imágenes de celda directamente en el fondo.
Ahora el proceso de carga:
Solo incluye:
#import "ManabImageOperations.h"
El código es el siguiente para una celda:
ManabImageOperations.h:
ManabImageOperations.m:
Verifique la respuesta y comente si se produce algún problema ...
fuente
Simplemente cambia,
Dentro
fuente
Simplemente puede pasar su URL,
fuente
fuente