Caricamento asincrono dell'immagine dall'URL all'interno di una cella UITableView: l'immagine cambia in un'immagine errata durante lo scorrimento


158

Ho scritto due modi per caricare in modo asincrono le immagini nella mia cella UITableView. In entrambi i casi l'immagine verrà caricata correttamente, ma quando scorro la tabella le immagini cambieranno alcune volte fino a quando lo scorrimento terminerà e l'immagine tornerà all'immagine giusta. Non ho idea del perché questo stia accadendo.

#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
Stai tentando di archiviare informazioni nelle celle effettive. Questo è male, molto male. Dovresti archiviare le informazioni in n array (o qualcosa di simile) e quindi visualizzarle nelle celle. Le informazioni in questo caso sono il vero UIImage. Sì, caricarlo in modo asincrono ma caricarlo in un array.
Fogmeister,

1
@Fogmeister Ti riferisci a poster? È presumibilmente una visualizzazione di immagini nella sua cella personalizzata, quindi ciò che EXEC_BAD_ACCESS sta facendo è perfettamente giusto. Hai ragione a dire che non dovresti usare la cella come repository per i dati del modello, ma non credo sia quello che sta facendo. Sta solo dando alla cella personalizzata ciò di cui ha bisogno per presentarsi. Inoltre, e questo è un problema più sottile, sarei diffidente riguardo alla memorizzazione di un'immagine, stessa, nel tuo array di modelli che supporta la vista da tavolo. È meglio utilizzare un meccanismo di memorizzazione nella cache delle immagini e l'oggetto modello deve recuperare da quella cache.
Rob,

1
Sì, esattamente il mio punto. Osservando la richiesta (che viene mostrata per intero) sta scaricando l'immagine in modo asincrono e inserendola direttamente in imageView nella cella. (Usando così la cella per memorizzare i dati, cioè l'immagine). Quello che dovrebbe fare è fare riferimento a un oggetto e richiedere l'immagine da quell'oggetto (contenuto in un array o da qualche parte). Se l'oggetto non ha ancora l'immagine, dovrebbe restituire un segnaposto e scaricare l'immagine. Quindi quando l'immagine viene scaricata e pronta per essere visualizzata, informa la tabella in modo che possa aggiornare la cella (se è visibile).
Fogmeister,

1
Quello che sta facendo forzerà il download ogni volta che scorre fino a quella cella nella tabella. Se le immagini vengono archiviate in modo persistente dipende da lui, ma almeno memorizzarle per la durata della visualizzazione della tabella.
Fogmeister,

1
Esatto: D In questo modo devi solo recuperare l'immagine dall'URL una volta. Lo vedrai su cose come Facebook Friend Picker. All'inizio tutti gli avatar sono segnaposto grigi. Quindi, mentre scorri, si riempiono tutti mentre si muove. Ma poi quando si scorre indietro a una cella mostrata in precedenza, mostrerà immediatamente l'immagine già scaricata.
Fogmeister,

Risposte:


230

Supponendo che tu stia cercando una soluzione tattica rapida, ciò che devi fare è assicurarti che l'immagine della cella sia inizializzata e che la riga della cella sia ancora visibile, ad esempio:

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

Il codice sopra risolve alcuni problemi derivanti dal riutilizzo della cella:

  1. Non stai inizializzando l'immagine della cella prima di avviare la richiesta in background (il che significa che l'ultima immagine per la cella dequeued sarà ancora visibile mentre la nuova immagine viene scaricata). Accertati nildella imageproprietà di qualsiasi vista immagine altrimenti vedrai lo sfarfallio delle immagini.

  2. Un problema più sottile è che su una rete molto lenta, la tua richiesta asincrona potrebbe non finire prima che la cella scorra dallo schermo. Puoi usare il UITableViewmetodo cellForRowAtIndexPath:(da non confondere con il UITableViewDataSourcemetodo con lo stesso nome tableView:cellForRowAtIndexPath:) per vedere se la cella per quella riga è ancora visibile. Questo metodo restituirà nilse la cella non è visibile.

    Il problema è che la cella è scivolata via al termine del metodo asincrono e, peggio ancora, la cella è stata riutilizzata per un'altra riga della tabella. Controllando per vedere se la riga è ancora visibile, ti assicurerai di non aggiornare accidentalmente l'immagine con l'immagine per una riga che da allora è scivolata fuori dallo schermo.

  3. Un po 'estraneo alla domanda attuale, mi sono comunque sentito obbligato ad aggiornarlo per sfruttare le convenzioni e le API moderne, in particolare:

    • Utilizzare NSURLSessionanziché inviare -[NSData contentsOfURL:]a una coda in background;

    • Usa dequeueReusableCellWithIdentifier:forIndexPath:piuttosto che dequeueReusableCellWithIdentifier:(ma assicurati di usare il prototipo di cella o la classe di registro o NIB per quell'identificatore); e

    • Ho usato un nome di classe conforme alle convenzioni di denominazione del cacao (ovvero iniziare con la lettera maiuscola).

Anche con queste correzioni, ci sono problemi:

  1. Il codice sopra riportato non memorizza nella cache le immagini scaricate. Ciò significa che se si scorre un'immagine fuori dallo schermo e si torna sullo schermo, l'app potrebbe tentare di recuperare nuovamente l'immagine. Forse avrai la fortuna che le intestazioni di risposta del tuo server consentano la memorizzazione nella cache abbastanza trasparente offerta da NSURLSessione NSURLCache, in caso contrario, effettuerai richieste server non necessarie e offrirai un UX molto più lento.

  2. Non stiamo annullando le richieste per le celle che scorrono fuori dallo schermo. Pertanto, se si scorre rapidamente fino alla 100a riga, l'immagine per quella riga potrebbe essere arretrata rispetto alle richieste delle 99 precedenti righe che non sono più visibili. Devi sempre assicurarti di dare la priorità alle richieste di celle visibili per la migliore UX.

La soluzione più semplice che risolve questi problemi è l'utilizzo di una UIImageViewcategoria, ad esempio fornita con SDWebImage o AFNetworking . Se vuoi, puoi scrivere il tuo codice per affrontare i problemi di cui sopra, ma è un sacco di lavoro e le UIImageViewcategorie di cui sopra hanno già fatto questo per te.


1
Grazie. Credo che tu debba modificare la tua risposta. updateCell.poster.image = nilper cell.poster.image = nil;updateCell viene chiamato prima di essere dichiarato.
Segev,

1
La mia app usa molto json, quindi AFNetworkingè sicuramente la strada da percorrere. Lo sapevo ma era troppo pigro per usarlo. Sto solo ammirando il funzionamento della cache con la loro semplice riga di codice. [imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
Segev,

2
Ho provato tutto quanto sopra e SDWebImage (in realtà si è fermato qui e non ho nemmeno bisogno di provare AFNetworking) e questa è stata di gran lunga la scelta migliore. Grazie @Rob.
mondousage,

1
Al termine del caricamento dell'immagine e dell'aggiornamento della vista dell'immagine, rimuovere l'indicatore di attività. L'unico trucco è che devi anticipare cosa succede se la cella scorre fuori dalla vista mentre l'immagine viene recuperata e la cella viene riutilizzata per un'altra riga, dovrai rilevare la presenza di qualsiasi indicatore di attività esistente e rimuovere / aggiornare esso, non solo supporre che la cella sia libera da un indicatore esistente.
Rob,

1
La domanda originale era "perché questo cellForRowAtIndexPathprovoca uno sfarfallio delle immagini quando scorro rapidamente" e ho spiegato perché ciò è accaduto e come risolverlo. Ma ho continuato a spiegare perché anche questo fosse insufficiente, ho descritto alcuni problemi più profondi e ho spiegato perché sarebbe meglio usare con una di quelle librerie per gestire questo con più grazia (dare priorità alle richieste di celle visibili, memorizzazione nella cache per evitare la rete ridondante richieste, ecc.). Non sono chiaro cos'altro ti aspettavi in ​​risposta alla domanda "come posso bloccare le immagini tremolanti nella vista della mia tabella".
Rob,

15

/ * L'ho fatto in questo modo e l'ho testato * /

Passaggio 1 = Registra la classe di cella personalizzata (in caso di cella prototipo nella tabella) o pennino (in caso di pennino personalizzato per cella personalizzata) per tabella come questa nel metodo viewDidLoad:

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

O

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

Passaggio 2 = Utilizzare il metodo "dequeueReusableCellWithIdentifier: forIndexPath:" di UITableView (per questo, è necessario registrare la classe o il pennino):

   - (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
La chiamata a cellForRowAtIndexPath all'interno del blocco finale non fa sì che il tutto venga licenziato una seconda volta?
Mark Bridges,

@MarkBridges, No. In realtà sto chiamando il metodo cellForRowAtIndexPath di tableView qui. Non confonderti con il metodo dell'origine dati di tableView con lo stesso nome. È necessario, può essere chiamato come [self tableView: tableView cellForRowAtIndexPath: indexPath]; Spero che questo cancella la tua confusione.
Nitesh Borad,

14

Esistono più framework che risolvono questo problema. Solo per citarne alcuni:

Swift:

Objective-C:


Si prega di aggiungere i vostri suggerimenti se ci sono altri quadri da considerare.
Kean,

3
In realtà non SDWebImagerisolve questo problema. Sei in grado di controllare quando l'immagine viene scaricata, ma SDWebImageassegna l'immagine UIImageViewsenza chiederti il ​​permesso di farlo. Fondamentalmente, il problema dalla domanda non è ancora risolto con questa libreria.
Bartłomiej Semańczyk,

Il problema della domanda era che l'autore non stava controllando se la cella era stata riutilizzata o meno. Questo è un molto problema di fondo che è indirizzata dai quadri, tra cui SDWebImage.
Kean,

SDWebImage è molto ritardato da iOS 8, era uno dei miei framework preferiti ma ora sto iniziando a usare PinRemoteImage che funziona davvero bene.
Joan Cardona,

@ BartłomiejSemańczyk Hai ragione, questo problema non è stato risolto da SDWebimage
Jan

9

Swift 3

Scrivo la mia implementazione leggera per il caricatore di immagini con l'utilizzo di NSCache. Nessuno sfarfallio dell'immagine delle celle!

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()
        }
    }
}

Esempio di utilizzo

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
direi grazie a te. ma il codice ha un piccolo problema. if let data = try? Dati (contentsOf: url) {// si prega di sostituire l'URL nella posizione. aiuterebbe molte persone.
Carl Hung,

2
Con il codice così com'è, scarichi due volte il file sulla rete: una volta in downloadTaks, una volta con i dati (cntentsOf :). È necessario l'ubicazione dell'utente al posto dell'URL perché l'attività di download si scarica semplicemente sulla rete e scrive i dati in un file temporaneo e ti passa localUrl (posizione nel tuo caso). Pertanto, Data deve puntare all'URL locale in modo che legga solo dal file.
Stéphane de Luca,

Nell'esempio di utilizzo, dovrebbe essere "ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) ......."?
Tim Kruger,

non funzionerà durante lo scorrimento molto veloce, le immagini si scambieranno molte volte a causa del riutilizzo delle celle.
Juan Boero,

5

Ecco la versione rapida (usando il codice obiettivo C @ obiettivo @Nitesh): -

   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 risposta migliore non è il modo corretto per farlo :(. In realtà hai associato indexPath al modello, il che non è sempre buono. Immagina che alcune righe siano state aggiunte durante il caricamento dell'immagine. Ora la cella per un dato IndexPath esiste sullo schermo, ma l'immagine non è più corretto! La situazione è piuttosto improbabile e difficile da replicare ma è possibile.

È meglio usare l'approccio MVVM, associare la cella con viewModel nel controller e caricare l'immagine in viewModel (assegnando il segnale ReactiveCocoa con il metodo switchToLatest), quindi sottoscrivere questo segnale e assegnare l'immagine alla cella! ;)

Devi ricordare di non abusare di MVVM. Le viste devono essere semplicissime! Considerando che ViewModels dovrebbe essere riutilizzabile! Ecco perché è molto importante associare View (UITableViewCell) e ViewModel nel controller.


1
Sì, il mio percorso dell'indice "correzione tattica" (che non stavo raccomandando, ma era solo la modifica più modesta per risolvere il problema di OP) soffre di questo problema (ma solo se la vista da tavolo continua ad avere le righe aggiunte / cancellate). E se questo fenomeno si manifestasse, potrei risolverlo in altri modi (piuttosto che cercare usando lo stesso percorso dell'indice, basta interrogare il modello per la riga appropriata). Ma quella soluzione tattica ha problemi ancora più eclatanti (che descrivo sopra) di quello che sollevi qui. Se si utilizza la UIImageViewsoluzione di categoria che consiglio, non vi è alcun problema relativo ai percorsi dell'indice.
Rob,

2
Potrei sembrare un po 'pedante, ma invocare qualsiasi tipo di logica da VIEW sta abusando di questa architettura.
badeleux,

3

Nel mio caso, non era dovuto alla memorizzazione delle immagini nella cache (Usato SDWebImage). È stato a causa della mancata corrispondenza del tag della cella personalizzata con indexPath.row.

Su cellForRowAtIndexPath:

1) Assegna un valore di indice alla tua cella personalizzata. Per esempio,

cell.tag = indexPath.row

2) Sul thread principale, prima di assegnare l'immagine, controlla se l'immagine appartiene alla cella corrispondente abbinandola al tag.

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

2

Grazie "Rob" .... Ho avuto lo stesso problema con UICollectionView e la tua risposta mi ha aiutato a risolvere il mio problema. Ecco il mio codice:

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

Per me, mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];non è mai zero, quindi questo non ha alcun effetto.
carbocation

1
puoi verificare che la tua cella sia visibile o meno: for (mycell * updateCell in collectionView.visibleCells) {cellVisible = YES; } if (cellVisible) {cell.coverImageView.imageURL = [NSURL URLWithString: [Dict valueForKey: @ "ImageURL"]]; } Funziona anche per me
sneha,

@sneha Sì, puoi vedere se è visibile iterando in visibleCellsquesto modo, ma sospetto che l'uso [collectionView cellForItemAtIndexPath:indexPath]sia più efficiente (ed è per questo che fai quella chiamata in primo luogo).
Rob,

@sneha Inoltre, nel tuo esempio di codice in questa risposta, sopra, controlli per vedere se updateCellnon lo è nil, ma non lo usi. Dovresti usarlo non solo per determinare se la cella della vista di raccolta è ancora visibile, ma dovresti quindi utilizzare updateCellall'interno di questo blocco, non cell(che potrebbe non essere più valido). E ovviamente, se lo è nil, non devi fare nulla (perché questa cella non è visibile).
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

Penso che tu voglia velocizzare il caricamento della tua cella al momento del caricamento dell'immagine per la cella in background. Per questo abbiamo fatto i seguenti passi:

  1. Il controllo del file esiste nella directory del documento oppure no.

  2. In caso contrario, caricare l'immagine per la prima volta e salvarla nella nostra directory dei documenti del telefono. Se non desideri salvare l'immagine nel telefono, puoi caricare le immagini delle celle direttamente sullo sfondo.

  3. Ora il processo di caricamento:

Includi solo: #import "ManabImageOperations.h"

Il codice è come sotto per una cella:

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

Controlla la risposta e commenta se si verificano problemi ....


0

Cambia semplicemente,

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

In

    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

Puoi semplicemente passare il tuo 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];

posso sapere il motivo?
Utente 558

-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
I dump del codice senza alcuna spiegazione sono raramente utili. Ti preghiamo di considerare di modificare questa risposta per fornire un contesto.
Chris,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.