J'ai écrit deux façons de charger des images de manière asynchrone dans ma cellule UITableView. Dans les deux cas, l'image se chargera correctement, mais lorsque je ferai défiler le tableau, les images changeront plusieurs fois jusqu'à ce que le défilement se termine et que l'image revienne à l'image de droite. Je n'ai aucune idée de pourquoi cela se produit.
#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
? Il s'agit probablement d'une vue d'image dans sa cellule personnalisée, donc ce que fait EXEC_BAD_ACCESS est parfaitement correct. Vous avez raison de ne pas utiliser la cellule comme référentiel pour les données de modèle, mais je ne pense pas que ce soit ce qu'il fait. Il donne juste à la cellule personnalisée ce dont elle a besoin pour se présenter. De plus, et c'est un problème plus subtil, je me méfierais du stockage d'une image, elle-même, dans votre tableau de modèles sauvegardant votre tableview. Il est préférable d'utiliser un mécanisme de mise en cache d'image et votre objet de modèle doit récupérer à partir de ce cache.Réponses:
En supposant que vous recherchez une solution tactique rapide, ce que vous devez faire est de vous assurer que l'image de la cellule est initialisée et que la ligne de la cellule est toujours visible, par exemple:
Le code ci-dessus résout quelques problèmes liés au fait que la cellule est réutilisée:
Vous n'initialisez pas l'image de la cellule avant de lancer la demande d'arrière-plan (ce qui signifie que la dernière image de la cellule retirée de la file d'attente sera toujours visible pendant le téléchargement de la nouvelle image). Assurez-vous de
nil
laimage
propriété de toutes les vues d'image, sinon vous verrez le scintillement des images.Un problème plus subtil est que sur un réseau très lent, votre demande asynchrone peut ne pas se terminer avant que la cellule ne sorte de l'écran. Vous pouvez utiliser la
UITableView
méthodecellForRowAtIndexPath:
(à ne pas confondre avec laUITableViewDataSource
méthode de nom similairetableView:cellForRowAtIndexPath:
) pour voir si la cellule de cette ligne est toujours visible. Cette méthode retourneranil
si la cellule n'est pas visible.Le problème est que la cellule a défilé au moment où votre méthode asynchrone est terminée et, pire encore, la cellule a été réutilisée pour une autre ligne du tableau. En vérifiant si la ligne est toujours visible, vous vous assurerez de ne pas mettre à jour accidentellement l'image avec l'image d'une ligne qui a depuis défilé hors de l'écran.
Quelque peu sans rapport avec la question posée, je me sentais toujours obligé de mettre à jour cela pour tirer parti des conventions modernes et de l'API, notamment:
Utilisez
NSURLSession
plutôt que de distribuer-[NSData contentsOfURL:]
à une file d'attente d'arrière-plan;Utilisez
dequeueReusableCellWithIdentifier:forIndexPath:
plutôt quedequeueReusableCellWithIdentifier:
(mais assurez-vous d'utiliser le prototype de cellule ou la classe de registre ou NIB pour cet identifiant); etJ'ai utilisé un nom de classe conforme aux conventions de dénomination de Cocoa (c'est-à-dire commencer par la lettre majuscule).
Même avec ces corrections, il y a des problèmes:
Le code ci-dessus ne met pas en cache les images téléchargées. Cela signifie que si vous faites défiler une image hors de l'écran et de nouveau sur l'écran, l'application peut essayer de récupérer l'image à nouveau. Peut-être aurez-vous la chance que les en-têtes de réponse de votre serveur permettent la mise en cache assez transparente offerte par
NSURLSession
etNSURLCache
, mais sinon, vous ferez des demandes de serveur inutiles et offrirez une UX beaucoup plus lente.Nous n'annulons pas les demandes de cellules qui défilent hors de l'écran. Ainsi, si vous faites défiler rapidement jusqu'à la 100e ligne, l'image de cette ligne pourrait être en retard par rapport aux demandes des 99 lignes précédentes qui ne sont même plus visibles. Vous voulez toujours vous assurer de prioriser les demandes de cellules visibles pour la meilleure UX.
La solution la plus simple qui résout ces problèmes consiste à utiliser une
UIImageView
catégorie, telle que celle fournie avec SDWebImage ou AFNetworking . Si vous le souhaitez, vous pouvez écrire votre propre code pour résoudre les problèmes ci-dessus, mais c'est beaucoup de travail, et lesUIImageView
catégories ci-dessus l' ont déjà fait pour vous.la source
updateCell.poster.image = nil
tocell.poster.image = nil;
updateCell est appelé avant d'être déclaré.AFNetworking
c'est donc la voie à suivre. J'étais au courant mais j'étais trop paresseux pour l'utiliser. J'admire simplement comment la mise en cache fonctionne avec leur simple ligne de code.[imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
cellForRowAtIndexPath
entraîne-t-il un scintillement des images lorsque je fais défiler rapidement" et j'ai expliqué pourquoi cela s'est produit ainsi que comment y remédier. Mais j'ai continué en expliquant pourquoi même cela était insuffisant, en décrivant quelques problèmes plus profonds et en expliquant pourquoi vous feriez mieux d'utiliser l'une de ces bibliothèques pour gérer cela plus gracieusement (prioriser les demandes de cellules visibles, la mise en cache pour éviter un réseau redondant demandes, etc.). Je ne sais pas ce à quoi vous vous attendiez en réponse à la question "comment puis-je arrêter les images scintillantes dans ma vue de tableau"./ * Je l'ai fait de cette façon, et je l'ai également testé * /
Étape 1 = Enregistrez la classe de cellule personnalisée (en cas de cellule prototype dans le tableau) ou la pointe (en cas de pointe personnalisée pour la cellule personnalisée) pour la table comme celle-ci dans la méthode viewDidLoad:
OU
Étape 2 = Utilisez la méthode "dequeueReusableCellWithIdentifier: forIndexPath:" de UITableView comme ceci (pour cela, vous devez enregistrer la classe ou la nib):
la source
Il existe plusieurs cadres qui résolvent ce problème. Juste pour en nommer quelques-uns:
Rapide:
Objectif c:
la source
SDWebImage
résout pas ce problème. Vous pouvez contrôler le moment où l'image est téléchargée, maisSDWebImage
attribuez-lui l'imageUIImageView
sans vous demander l'autorisation de le faire. Fondamentalement, le problème de la question n'est toujours pas résolu avec cette bibliothèque.Swift 3
J'écris ma propre implémentation légère pour le chargeur d'image avec l'utilisation de NSCache. Aucune image de cellule ne scintille!
ImageCacheLoader.swift
Exemple d'utilisation
la source
Voici la version swift (en utilisant le code objectif C @Nitesh Borad): -
la source
La meilleure réponse n'est pas la bonne façon de procéder: (. Vous avez en fait lié indexPath avec model, ce qui n'est pas toujours bon. Imaginez que certaines lignes ont été ajoutées lors du chargement de l'image. Maintenant, la cellule pour un indexPath donné existe à l'écran, mais l'image n'est plus correcte! La situation est un peu improbable et difficile à reproduire mais c'est possible.
Il est préférable d'utiliser l'approche MVVM, de lier la cellule avec viewModel dans le contrôleur et de charger l'image dans viewModel (attribution du signal ReactiveCocoa avec la méthode switchToLatest), puis souscrire ce signal et attribuer l'image à la cellule! ;)
Vous devez vous rappeler de ne pas abuser de MVVM. Les vues doivent être extrêmement simples! Alors que ViewModels devrait être réutilisable! C'est pourquoi il est très important de lier View (UITableViewCell) et ViewModel dans le contrôleur.
la source
UIImageView
solution de catégorie que je conseille, il n'y a pas de tel problème concernant les chemins d'index.Dans mon cas, ce n'était pas dû à la mise en cache d'image (utilisé SDWebImage). Cela était dû à une incompatibilité de balise de cellule personnalisée avec indexPath.row.
Sur cellForRowAtIndexPath:
1) Attribuez une valeur d'index à votre cellule personnalisée. Par exemple,
2) Sur le fil principal, avant d'attribuer l'image, vérifiez si l'image appartient à la cellule correspondante en la faisant correspondre avec la balise.
la source
Merci "Rob" .... J'ai eu le même problème avec UICollectionView et votre réponse m'aide à résoudre mon problème. Voici mon code:
la source
mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
n'est jamais nul, donc cela n'a aucun effet.visibleCells
comme ça, mais je pense que l'utilisation[collectionView cellForItemAtIndexPath:indexPath]
est plus efficace (et c'est pourquoi vous faites cet appel en premier lieu).updateCell
n'est pas le casnil
, mais vous ne l'utilisez pas. Vous devez l'utiliser non seulement pour déterminer si la cellule de vue de collection est toujours visible, mais vous devez ensuite l'utiliser à l'updateCell
intérieur de ce bloc, noncell
(ce qui peut ne plus être valide). Et évidemment, si c'est le casnil
, vous n'avez rien à faire (car cette cellule n'est pas visible).la source
Je pense que vous voulez accélérer le chargement de votre cellule au moment du chargement de l'image pour la cellule en arrière-plan. Pour cela, nous avons effectué les étapes suivantes:
Vérifier que le fichier existe ou non dans le répertoire des documents.
Sinon, chargez l'image pour la première fois et enregistrez-la dans notre répertoire de documents téléphoniques. Si vous ne souhaitez pas enregistrer l'image dans le téléphone, vous pouvez charger des images de cellule directement en arrière-plan.
Maintenant, le processus de chargement:
Incluez simplement:
#import "ManabImageOperations.h"
Le code est comme ci-dessous pour une cellule:
ManabImageOperations.h:
ManabImageOperations.m:
Veuillez vérifier la réponse et commenter s'il y a un problème ...
la source
Changez simplement,
Dans
la source
Vous pouvez simplement transmettre votre URL,
la source
la source