Chargement d'image asynchrone à partir de l'URL dans une cellule UITableView - l'image change en mauvaise image lors du défilement


158

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;
}

5
Vous essayez de stocker des informations dans les cellules réelles. C'est mauvais, très mauvais. Vous devez stocker les informations dans un tableau n (ou quelque chose de similaire), puis les afficher dans les cellules. Les informations dans ce cas sont la véritable UIImage. Oui, chargez-le de manière asynchrone mais chargez-le dans un tableau.
Fogmeister

1
@Fogmeister Parlez-vous 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.
Rob

1
Oui, exactement mon point. En regardant la demande (qui est affichée dans son intégralité), il télécharge l'image de manière asynchrone et la place directement dans l'imageView dans la cellule. (Ainsi en utilisant la cellule pour stocker les données, c'est à dire l'image). Ce qu'il devrait faire, c'est référencer un objet et demander l'image de cet objet (contenue dans un tableau ou quelque part). Si l'objet n'a pas encore l'image, il doit renvoyer un espace réservé et télécharger l'image. Ensuite, lorsque l'image est téléchargée et prête à être affichée, informez-en le tableau afin qu'il puisse mettre à jour la cellule (si elle est visible).
Fogmeister

1
Ce qu'il fait forcera le téléchargement à chaque fois qu'il fera défiler jusqu'à cette cellule du tableau. Que les images soient stockées de manière persistante dépend de lui, mais au moins les stocker pour la durée de vie de la tableview.
Fogmeister

1
Exactement: D De cette façon, vous n'avez besoin de récupérer l'image à partir de l'URL qu'une seule fois. Vous verrez cela sur des choses comme Facebook Friend Picker. Lorsque vous démarrez, tous les avatars sont des espaces réservés gris. Ensuite, au fur et à mesure que vous faites défiler, ils se remplissent tous au fur et à mesure de leur déplacement. Mais ensuite, lorsque vous revenez à une cellule précédemment affichée, elle affichera instantanément l'image déjà téléchargée.
Fogmeister

Réponses:


230

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:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

    cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            if (image) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)
                        updateCell.poster.image = image;
                });
            }
        }
    }];
    [task resume];

    return cell;
}

Le code ci-dessus résout quelques problèmes liés au fait que la cellule est réutilisée:

  1. 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 nilla imagepropriété de toutes les vues d'image, sinon vous verrez le scintillement des images.

  2. 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 UITableViewméthode cellForRowAtIndexPath:(à ne pas confondre avec la UITableViewDataSourceméthode de nom similaire tableView:cellForRowAtIndexPath:) pour voir si la cellule de cette ligne est toujours visible. Cette méthode retournera nilsi 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.

  3. 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 NSURLSessionplutôt que de distribuer -[NSData contentsOfURL:]à une file d'attente d'arrière-plan;

    • Utilisez dequeueReusableCellWithIdentifier:forIndexPath:plutôt que dequeueReusableCellWithIdentifier:(mais assurez-vous d'utiliser le prototype de cellule ou la classe de registre ou NIB pour cet identifiant); et

    • J'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:

  1. 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 NSURLSessionet NSURLCache, mais sinon, vous ferez des demandes de serveur inutiles et offrirez une UX beaucoup plus lente.

  2. 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 UIImageViewcaté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 les UIImageViewcatégories ci-dessus l' ont déjà fait pour vous.


1
Merci. Je pense que vous devez modifier votre réponse. updateCell.poster.image = nilto cell.poster.image = nil;updateCell est appelé avant d'être déclaré.
Segev

1
Mon application utilise beaucoup de json, AFNetworkingc'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 *)#>];
Segev

2
J'ai essayé tout ce qui précède et SDWebImage (en fait arrêté ici et n'a même pas besoin d'essayer AFNetworking) et c'était de loin le meilleur choix. Merci @Rob.
mondousage

1
Lorsque vous avez terminé de charger l'image et de mettre à jour la vue de l'image, supprimez l'indicateur d'activité. La seule astuce est que vous devez anticiper ce qui se passe si la cellule défile hors de la vue pendant que l'image est en cours de récupération et que la cellule est réutilisée pour une autre ligne, vous devrez détecter la présence de tout indicateur d'activité existant et supprimer / mettre à jour il ne s'agit pas simplement de supposer que la cellule ne contient pas d'indicateur existant.
Rob

1
La question initiale était "pourquoi cela cellForRowAtIndexPathentraî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".
Rob

15

/ * 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:

[self.yourTableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"CustomCell"];

OU

[self.yourTableView registerNib:[UINib nibWithNibName:@"CustomTableViewCell" bundle:nil] forCellReuseIdentifier:@"CustomCell"];

Étape 2 = Utilisez la méthode "dequeueReusableCellWithIdentifier: forIndexPath:" de UITableView comme ceci (pour cela, vous devez enregistrer la classe ou la nib):

   - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
            CustomTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell" forIndexPath:indexPath];

            cell.imageViewCustom.image = nil; // [UIImage imageNamed:@"default.png"];
            cell.textLabelCustom.text = @"Hello";

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // retrive image on global queue
                UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL:     [NSURL URLWithString:kImgLink]]];

                dispatch_async(dispatch_get_main_queue(), ^{

                    CustomTableViewCell * cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
                  // assign cell image on main thread
                    cell.imageViewCustom.image = img;
                });
            });

            return cell;
        }

1
L'appel de cellForRowAtIndexPath dans le bloc final ne provoque-t-il pas le déclenchement de l'ensemble une deuxième fois?
Mark Bridges

@MarkBridges, Non. En fait, j'appelle la méthode cellForRowAtIndexPath de tableView ici. Ne vous méprenez pas avec la méthode de source de données de tableView portant le même nom. Il est nécessaire, il peut être appelé comme [self tableView: tableView cellForRowAtIndexPath: indexPath]; J'espère que cela dissipera votre confusion.
Nitesh Borad

14

Il existe plusieurs cadres qui résolvent ce problème. Juste pour en nommer quelques-uns:

Rapide:

Objectif c:


Veuillez ajouter vos suggestions s'il existe d'autres cadres qui méritent d'être pris en considération.
kean

3
En fait, ne SDWebImagerésout pas ce problème. Vous pouvez contrôler le moment où l'image est téléchargée, mais SDWebImageattribuez-lui l'image UIImageViewsans vous demander l'autorisation de le faire. Fondamentalement, le problème de la question n'est toujours pas résolu avec cette bibliothèque.
Bartłomiej Semańczyk

Le problème de la question était que l'auteur ne vérifiait pas si la cellule était réutilisée ou non. C'est un problème très basique qui est résolu par ces frameworks, y compris SDWebImage.
kean

SDWebImage est très lent depuis iOS 8, c'était l'un de mes frameworks favoris mais maintenant je commence à utiliser PinRemoteImage qui fonctionne vraiment bien.
Joan Cardona

@ BartłomiejSemańczyk Vous avez raison, ce problème n'est pas résolu par SDWebimage
janvier

9

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

typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())

class ImageCacheLoader {
    
    var task: URLSessionDownloadTask!
    var session: URLSession!
    var cache: NSCache<NSString, UIImage>!
    
    init() {
        session = URLSession.shared
        task = URLSessionDownloadTask()
        self.cache = NSCache()
    }
    
    func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) {
        if let image = self.cache.object(forKey: imagePath as NSString) {
            DispatchQueue.main.async {
                completionHandler(image)
            }
        } else {
            /* You need placeholder image in your assets, 
               if you want to display a placeholder to user */
            let placeholder = #imageLiteral(resourceName: "placeholder")
            DispatchQueue.main.async {
                completionHandler(placeholder)
            }
            let url: URL! = URL(string: imagePath)
            task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
                if let data = try? Data(contentsOf: url) {
                    let img: UIImage! = UIImage(data: data)
                    self.cache.setObject(img, forKey: imagePath as NSString)
                    DispatchQueue.main.async {
                        completionHandler(img)
                    }
                }
            })
            task.resume()
        }
    }
}

Exemple d'utilisation

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")
    
    cell.title = "Cool title"

    imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
        // Before assigning the image, check whether the current cell is visible
        if let updateCell = tableView.cellForRow(at: indexPath) {
            updateCell.imageView.image = image
        }
    }    
    return cell
}

3
je voudrais vous dire merci. mais le code a un petit problème. si laissez les données = essayer? Data (contentsOf: url) {// veuillez remplacer l'url par l'emplacement. cela aiderait beaucoup de gens.
Carl Hung

2
Avec le code tel quel, vous téléchargez deux fois le fichier sur le réseau: une fois dans les downloadTaks, une fois avec les données (cntentsOf :). Vous devez utiliser l'emplacement à la place de l'url, car la tâche de téléchargement se télécharge simplement sur le réseau et écrit les données dans un fichier temporaire et vous transmet localUrl (emplacement dans votre cas). Les données doivent donc pointer vers l'URL locale afin qu'elle ne lise qu'à partir du fichier.
Stéphane de Luca

Dans l'exemple d'utilisation, est-il censé être "ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) ......."?
Tim Kruger

ne fonctionnera pas lors d'un défilement très rapide, les images seront échangées plusieurs fois en raison de la réutilisation des cellules.
Juan Boero le

5

Voici la version swift (en utilisant le code objectif C @Nitesh Borad): -

   if let img: UIImage = UIImage(data: previewImg[indexPath.row]) {
                cell.cardPreview.image = img
            } else {
                // The image isn't cached, download the img data
                // We should perform this in a background thread
                let imgURL = NSURL(string: "webLink URL")
                let request: NSURLRequest = NSURLRequest(URL: imgURL!)
                let session = NSURLSession.sharedSession()
                let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
                    let error = error
                    let data = data
                    if error == nil {
                        // Convert the downloaded data in to a UIImage object
                        let image = UIImage(data: data!)
                        // Store the image in to our cache
                        self.previewImg[indexPath.row] = data!
                        // Update the cell
                        dispatch_async(dispatch_get_main_queue(), {
                            if let cell: YourTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as? YourTableViewCell {
                                cell.cardPreview.image = image
                            }
                        })
                    } else {
                        cell.cardPreview.image = UIImage(named: "defaultImage")
                    }
                })
                task.resume()
            }

3

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.


1
Oui, mon chemin d'index "correction tactique" (que je ne recommandais pas, mais était plutôt la modification la plus modeste pour résoudre le problème d'OP) souffre de ce problème (mais seulement si la vue de table continue d'avoir des lignes ajoutées / supprimées). Et si ce phénomène se manifestait, je pourrais corriger cela d'autres manières (plutôt que de rechercher en utilisant le même chemin d'index, juste un modèle de requête pour la ligne appropriée). Mais cette solution tactique pose des problèmes encore plus graves (que je décris ci-dessus) que celui que vous soulevez ici. Si vous utilisez la UIImageViewsolution de catégorie que je conseille, il n'y a pas de tel problème concernant les chemins d'index.
Rob

2
Je peux paraître un peu pédant, mais invoquer toute sorte de logique de VIEW abuse de cette architecture.
badeleux

3

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,

cell.tag = indexPath.row

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.

dispatch_async(dispatch_get_main_queue(), ^{
   if(cell.tag == indexPath.row) {
     UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
     thumbnailImageView.image = tmpImage;
   }});
});

2

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:

 if ([Dict valueForKey:@"ImageURL"] != [NSNull null])
    {
        cell.coverImageView.image = nil;
        cell.coverImageView.imageURL=nil;

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            if ([Dict valueForKey:@"ImageURL"] != [NSNull null] )
            {
                dispatch_async(dispatch_get_main_queue(), ^{

                    myCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];

                    if (updateCell)
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;

                        cell.coverImageView.imageURL=[NSURL URLWithString:[Dict valueForKey:@"ImageURL"]];

                    }
                    else
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;
                    }


                });
            }
        });

    }
    else
    {
        cell.coverImageView.image=[UIImage imageNamed:@"default_cover.png"];
    }

Pour moi, ce mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];n'est jamais nul, donc cela n'a aucun effet.
carbocation le

1
vous pouvez vérifier que votre cellule est visible ou non par: for (mycell * updateCell in collectionView.visibleCells) {cellVisible = YES; } if (cellVisible) {cell.coverImageView.imageURL = [NSURL URLWithString: [Dict valueForKey: @ "ImageURL"]]; } Cela fonctionne aussi pour moi
sneha

@sneha Oui, vous pouvez vérifier qu'il est visible en itérant visibleCellscomme ça, mais je pense que l'utilisation [collectionView cellForItemAtIndexPath:indexPath]est plus efficace (et c'est pourquoi vous faites cet appel en premier lieu).
Rob

@sneha De plus, dans votre exemple de code dans cette réponse, ci-dessus, vous vérifiez si ce updateCelln'est pas le cas nil, 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' updateCellintérieur de ce bloc, non cell(ce qui peut ne plus être valide). Et évidemment, si c'est le cas nil, vous n'avez rien à faire (car cette cellule n'est pas visible).
Rob

2
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
        MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

        cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

        NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (data) {
                UIImage *image = [UIImage imageWithData:data];
                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                        if (updateCell)
                            updateCell.poster.image = image;
                    });
                }
            }
        }];
        [task resume];

        return cell;
    }

0

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:

  1. Vérifier que le fichier existe ou non dans le répertoire des documents.

  2. 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.

  3. Maintenant, le processus de chargement:

Incluez simplement: #import "ManabImageOperations.h"

Le code est comme ci-dessous pour une cellule:

NSString *imagestr=[NSString stringWithFormat:@"http://www.yourlink.com/%@",[dictn objectForKey:@"member_image"]];

        NSString *docDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
        NSLog(@"Doc Dir: %@",docDir);

        NSString  *pngFilePath = [NSString stringWithFormat:@"%@/%@",docDir,[dictn objectForKey:@"member_image"]];

        BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pngFilePath];
        if (fileExists)
        {
            [cell1.memberimage setImage:[UIImage imageWithContentsOfFile:pngFilePath] forState:UIControlStateNormal];
        }
        else
        {
            [ManabImageOperations processImageDataWithURLString:imagestr andBlock:^(NSData *imageData)
             {
                 [cell1.memberimage setImage:[[UIImage alloc]initWithData: imageData] forState:UIControlStateNormal];
                [imageData writeToFile:pngFilePath atomically:YES];
             }];
}

ManabImageOperations.h:

#import <Foundation/Foundation.h>

    @interface ManabImageOperations : NSObject
    {
    }
    + (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
    @end

ManabImageOperations.m:

#import "ManabImageOperations.h"
#import <QuartzCore/QuartzCore.h>
@implementation ManabImageOperations

+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_main_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);
        });
    });
  //  downloadQueue=nil;
    dispatch_release(downloadQueue);

}
@end

Veuillez vérifier la réponse et commenter s'il y a un problème ...


0

Changez simplement,

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];
     });
 });

Dans

    dispatch_async(kBgQueue, ^{
         NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
         cell.poster.image = [UIImage imageWithData:imgData];
         dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
         });
     });

0

Vous pouvez simplement transmettre votre URL,

NSURL *url = [NSURL URLWithString:@"http://www.myurl.com/1.png"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data,    NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                    yourimageview.image = image;
            });
        }
    }
}];
[task resume];

puis-je connaître la raison?
User558

-1
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    Static NSString *CellIdentifier = @"Cell";
    QTStaffViewCell *cell = (QTStaffViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    If (cell == nil)
    {

        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"QTStaffViewCell" owner:self options:nil];
        cell = [nib objectAtIndex: 0];

    }

    StaffData = [self.staffArray objectAtIndex:indexPath.row];
    NSString *title = StaffData.title;
    NSString *fName = StaffData.firstname;
    NSString *lName = StaffData.lastname;

    UIFont *FedSanDemi = [UIFont fontWithName:@"Aller" size:18];
    cell.drName.text = [NSString stringWithFormat:@"%@ %@ %@", title,fName,lName];
    [cell.drName setFont:FedSanDemi];

    UIFont *aller = [UIFont fontWithName:@"Aller" size:14];
    cell.drJob.text = StaffData.job;
    [cell.drJob setFont:aller];

    if ([StaffData.title isEqualToString:@"Dr"])
    {
        cell.drJob.frame = CGRectMake(83, 26, 227, 40);
    }
    else
    {
        cell.drJob.frame = CGRectMake(90, 26, 227, 40);

    }

    if ([StaffData.staffPhoto isKindOfClass:[NSString class]])
    {
        NSURL *url = [NSURL URLWithString:StaffData.staffPhoto];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
                completionHandler:^(NSURL *location,NSURLResponse *response, NSError *error) {

      NSData *imageData = [NSData dataWithContentsOfURL:location];
      UIImage *image = [UIImage imageWithData:imageData];

      dispatch_sync(dispatch_get_main_queue(),
             ^{
                    cell.imageView.image = image;
              });
    }];
        [task resume];
    }
       return cell;}

2
Les vidages de code sans aucune explication sont rarement utiles. Veuillez envisager de modifier cette réponse pour fournir un contexte.
Chris
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.