Asynchrones Laden von Bildern aus der URL in einer UITableView-Zelle - Das Bild ändert sich beim Scrollen in ein falsches Bild


158

Ich habe zwei Möglichkeiten zum asynchronen Laden von Bildern in meine UITableView-Zelle geschrieben. In beiden Fällen wird das Bild gut geladen, aber wenn ich durch die Tabelle scrolle, ändern sich die Bilder einige Male, bis der Bildlauf endet und das Bild zum richtigen Bild zurückkehrt. Ich habe keine Ahnung, warum das passiert.

#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
Sie versuchen, Informationen in den tatsächlichen Zellen zu speichern. Das ist schlecht, sehr schlecht. Sie sollten Informationen in n Arrays (oder ähnlichem) speichern und dann in den Zellen anzeigen. Die Information in diesem Fall ist das tatsächliche UIImage. Ja, laden Sie es asynchron, aber laden Sie es in ein Array.
Fogmeister

1
@Fogmeister Beziehen Sie sich poster? Das ist vermutlich eine Bildansicht in seiner benutzerdefinierten Zelle. Was EXEC_BAD_ACCESS also tut, ist vollkommen richtig. Sie haben Recht, dass Sie die Zelle nicht als Repository für Modelldaten verwenden sollten, aber ich glaube nicht, dass er das tut. Er gibt der benutzerdefinierten Zelle nur das, was sie braucht, um sich zu präsentieren. Darüber hinaus, und dies ist ein subtileres Problem, wäre ich vorsichtig, wenn ich ein Bild selbst in Ihrem Modellarray speichern würde, das Ihre Tabellenansicht unterstützt. Es ist besser, einen Bild-Caching-Mechanismus zu verwenden, und Ihr Modellobjekt sollte aus diesem Cache abgerufen werden.
Rob

1
Ja, genau mein Punkt. Wenn er sich die Anfrage ansieht (die vollständig angezeigt wird), lädt er das Bild asynchron herunter und fügt es direkt in die Bildansicht in der Zelle ein. (Verwenden Sie also die Zelle zum Speichern der Daten, dh des Bildes). Was er tun sollte, ist ein Objekt zu referenzieren und das Bild von diesem Objekt anzufordern (in einem Array oder irgendwo enthalten). Wenn das Objekt das Bild noch nicht hat, sollte es einen Platzhalter zurückgeben und das Bild herunterladen. Wenn das Bild heruntergeladen und zur Anzeige bereit ist, teilen Sie dies der Tabelle mit, damit die Zelle aktualisiert werden kann (sofern sie sichtbar ist).
Fogmeister

1
Was er tut, erzwingt den Download jedes Mal, wenn er zu dieser Zelle in der Tabelle blättert. Ob die Bilder dauerhaft gespeichert werden, liegt bei ihm, aber zumindest für die Lebensdauer der Tabellenansicht.
Fogmeister

1
Genau: D Auf diese Weise müssen Sie das Bild nur einmal von der URL abrufen. Sie werden dies auf Dingen wie Facebook Friend Picker sehen. Wenn Sie es starten, sind alle Avatare graue Platzhalter. Wenn Sie dann scrollen, füllen sich alle aus, während es sich bewegt. Wenn Sie jedoch zu einer zuvor angezeigten Zelle zurückblättern, wird sofort das bereits heruntergeladene Bild angezeigt.
Fogmeister

Antworten:


230

Angenommen, Sie suchen nach einer schnellen taktischen Lösung, müssen Sie sicherstellen, dass das Zellenbild initialisiert ist und dass die Zellenzeile noch sichtbar ist, z.

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

Der obige Code behebt einige Probleme, die sich aus der Tatsache ergeben, dass die Zelle wiederverwendet wird:

  1. Sie initialisieren das Zellenbild nicht, bevor Sie die Hintergrundanforderung initiieren (was bedeutet, dass das letzte Bild für die aus der Warteschlange befindliche Zelle weiterhin sichtbar ist, während das neue Bild heruntergeladen wird). Achten Sie darauf, nildie imageEigenschaft aller Bildansichten oder sonst werden Sie das Flackern der Bilder sehen.

  2. Ein subtileres Problem ist, dass in einem sehr langsamen Netzwerk Ihre asynchrone Anforderung möglicherweise nicht beendet wird, bevor die Zelle vom Bildschirm gescrollt wird. Sie können die UITableViewMethode verwenden cellForRowAtIndexPath:(nicht zu verwechseln mit der ähnlich benannten UITableViewDataSourceMethode tableView:cellForRowAtIndexPath:), um festzustellen, ob die Zelle für diese Zeile noch sichtbar ist. Diese Methode wird zurückgegeben, nilwenn die Zelle nicht sichtbar ist.

    Das Problem ist, dass die Zelle bis zum Abschluss Ihrer asynchronen Methode einen Bildlauf durchgeführt hat und, schlimmer noch, die Zelle für eine weitere Zeile der Tabelle wiederverwendet wurde. Indem Sie überprüfen, ob die Zeile noch sichtbar ist, stellen Sie sicher, dass Sie das Bild nicht versehentlich mit dem Bild für eine Zeile aktualisieren, die seitdem vom Bildschirm gescrollt wurde.

  3. Etwas unabhängig von der vorliegenden Frage fühlte ich mich dennoch gezwungen, diese zu aktualisieren, um moderne Konventionen und APIs zu nutzen, insbesondere:

    • Verwenden Sie diese Option, NSURLSessionanstatt sie -[NSData contentsOfURL:]an eine Hintergrundwarteschlange zu senden.

    • Verwenden Sie dequeueReusableCellWithIdentifier:forIndexPath:statt dequeueReusableCellWithIdentifier:(aber stellen Sie sicher, dass Sie für diese Kennung einen Zellprototyp oder eine Registerklasse oder eine NIB verwenden). und

    • Ich habe einen Klassennamen verwendet, der den Namenskonventionen von Cocoa entspricht (dh mit dem Großbuchstaben beginnen).

Trotz dieser Korrekturen gibt es Probleme:

  1. Der obige Code speichert die heruntergeladenen Bilder nicht zwischen. Das heißt, wenn Sie ein Bild vom Bildschirm und wieder auf dem Bildschirm scrollen, versucht die App möglicherweise, das Bild erneut abzurufen. Vielleicht haben Sie das Glück, dass Ihre Server-Antwortheader das von NSURLSessionund angebotene ziemlich transparente Caching ermöglichen. Andernfalls stellen NSURLCacheSie unnötige Serveranforderungen und bieten eine viel langsamere Benutzeroberfläche.

  2. Wir stornieren keine Anfragen für Zellen, die vom Bildschirm scrollen. Wenn Sie also schnell zur 100. Zeile scrollen, kann das Bild für diese Zeile hinter den Anforderungen für die vorherigen 99 Zeilen zurückbleiben, die nicht einmal mehr sichtbar sind. Sie möchten immer sicherstellen, dass Sie Anforderungen für sichtbare Zellen für die beste UX priorisieren.

Die einfachste Lösung, mit der diese Probleme behoben werden können, ist die Verwendung einer UIImageViewKategorie, wie sie beispielsweise in SDWebImage oder AFNetworking bereitgestellt wird . Wenn Sie möchten, können Sie Ihren eigenen Code schreiben, um die oben genannten Probleme zu lösen. UIImageViewDies ist jedoch eine Menge Arbeit, und die oben genannten Kategorien haben dies bereits für Sie erledigt.


1
Vielen Dank. Ich glaube, Sie müssen Ihre Antwort bearbeiten. updateCell.poster.image = nilto cell.poster.image = nil;updateCell wird aufgerufen, bevor es deklariert wird.
Segev

1
Meine App verwendet viel JSON und AFNetworkingist definitiv der richtige Weg. Ich wusste davon, war aber zu faul, um es zu benutzen. Ich bewundere nur, wie das Cacheing mit ihrer einfachen Codezeile funktioniert. [imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
Segev

2
Versuchte alle oben genannten und SDWebImage (tatsächlich hier gestoppt und musste nicht einmal AFNetworking versuchen) und dies war bei weitem die beste Wahl. Danke @Rob.
Mondousage

1
Wenn Sie das Laden des Bildes abgeschlossen und die Bildansicht aktualisiert haben, entfernen Sie die Aktivitätsanzeige. Der einzige Trick besteht darin, dass Sie vorhersehen müssen, was passiert, wenn die Zelle beim Abrufen des Bildes nicht mehr sichtbar ist und die Zelle für eine andere Zeile wiederverwendet wird. Sie müssen das Vorhandensein einer vorhandenen Aktivitätsanzeige erkennen und diese entfernen / aktualisieren Es wird nicht nur davon ausgegangen, dass die Zelle frei von einem vorhandenen Indikator ist.
Rob

1
Die ursprüngliche Frage lautete: "Warum führt dies cellForRowAtIndexPathzu einem Flackern der Bilder, wenn ich schnell scrolle?" Und ich erklärte, warum dies passiert ist und wie es behoben werden kann. Aber ich erklärte weiter, warum selbst das nicht ausreichte, beschrieb einige tiefere Probleme und argumentierte, warum Sie besser mit einer dieser Bibliotheken arbeiten sollten, um dies eleganter zu handhaben (priorisieren Sie Anforderungen für sichtbare Zellen, Caching, um redundantes Netzwerk zu vermeiden Anfragen usw.). Ich bin mir nicht sicher, was Sie sonst noch als Antwort auf die Frage erwartet haben, wie ich die flackernden Bilder in meiner Tabellenansicht stoppen kann.
Rob

15

/ * Ich habe es so gemacht und es auch getestet * /

Schritt 1 = Registrieren Sie eine benutzerdefinierte Zellenklasse (im Fall einer Prototypzelle in der Tabelle) oder eine Schreibfeder (im Fall einer benutzerdefinierten Schreibfeder für eine benutzerdefinierte Zelle) für eine Tabelle wie diese in der viewDidLoad-Methode:

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

ODER

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

Schritt 2 = Verwenden Sie die Methode "dequeueReusableCellWithIdentifier: forIndexPath:" von UITableView wie folgt (hierfür müssen Sie die Klasse oder die Schreibfeder registrieren):

   - (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
Wird durch das Aufrufen von cellForRowAtIndexPath im letzten Block das Ganze nicht ein zweites Mal ausgelöst?
Mark Bridges

@ MarkBridges, Nein. Eigentlich rufe ich hier die cellForRowAtIndexPath-Methode von tableView auf. Verwechseln Sie nicht die gleichnamige Datenquellenmethode von tableView. Es ist erforderlich, es kann wie folgt aufgerufen werden: [self tableView: tableView cellForRowAtIndexPath: indexPath]; Hoffe, dies wird Ihre Verwirrung beseitigen.
Nitesh Borad

14

Es gibt mehrere Frameworks, die dieses Problem lösen. Nur um ein paar zu nennen:

Schnell:

Ziel c:


Bitte fügen Sie Ihre Vorschläge hinzu, wenn andere Frameworks in Betracht gezogen werden sollten.
Kean

3
Eigentlich SDWebImagetut dieses Problem lösen. Sie können steuern, wann das Bild heruntergeladen wird, aber SDWebImagedas Bild zuweisen, UIImageViewohne Sie nach der Erlaubnis dazu zu fragen. Grundsätzlich ist das Problem aus der Frage mit dieser Bibliothek noch nicht gelöst.
Bartłomiej Semańczyk

Das Problem bei der Frage war, dass der Autor nicht überprüfte, ob die Zelle wiederverwendet wurde oder nicht. Dies ist ein sehr grundlegendes Problem, das von diesen Frameworks, einschließlich SDWebImage, behoben wird.
Kean

SDWebImage ist seit iOS 8 sehr verzögert, es war eines meiner bevorzugten Frameworks, aber jetzt verwende ich PinRemoteImage, das wirklich gut funktioniert.
Joan Cardona

@ BartłomiejSemańczyk sind Sie richtig, löste dieses Problem doesnt von SDWebimage
Jan

9

Swift 3

Ich schreibe meine eigene Light-Implementierung für Image Loader mit NSCache. Kein flackerndes Zellbild!

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

Anwendungsbeispiel

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
Ich würde mich bei Ihnen bedanken. aber der Code hat ein kleines Problem. wenn Daten = versuchen lassen? Daten (Inhalt von: URL) {// Bitte ersetzen Sie die URL durch den Speicherort. es würde vielen Menschen helfen.
Carl Hung

2
Mit dem Code wie er ist, laden Sie die Datei zweimal über das Netzwerk herunter: einmal in den downloadTaks, einmal mit den Daten (cntentsOf :). Sie müssen den Standort des Benutzers anstelle der URL angeben, da die Download-Aufgabe einfach über das Netzwerk heruntergeladen und die Daten in eine temporäre Datei geschrieben wird und Sie die localUrl (in Ihrem Fall den Speicherort) übergeben. Daten müssen also auf die lokale URL verweisen, damit sie nur aus der Datei lesen können.
Stéphane de Luca

Soll es im Verwendungsbeispiel "ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) ......." sein?
Tim Kruger

funktioniert beim schnellen Scrollen nicht, Bilder werden aufgrund der Wiederverwendung von Zellen viele Male ausgetauscht.
Juan Boero

5

Hier ist die schnelle Version (unter Verwendung des @ Nitesh Borad-Ziel-C-Codes): -

   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

Die beste Antwort ist nicht der richtige Weg, dies zu tun :(. Sie haben indexPath tatsächlich mit model gebunden, was nicht immer gut ist. Stellen Sie sich vor, dass beim Laden des Bildes einige Zeilen hinzugefügt wurden. Jetzt ist die Zelle für den angegebenen indexPath auf dem Bildschirm vorhanden, aber das Bild ist nicht mehr richtig! Die Situation ist eher unwahrscheinlich und schwer zu replizieren, aber es ist möglich.

Es ist besser, den MVVM-Ansatz zu verwenden, die Zelle mit viewModel im Controller zu binden und das Bild in viewModel zu laden (ReactiveCocoa-Signal mit der switchToLatest-Methode zuweisen), dieses Signal dann zu abonnieren und der Zelle ein Bild zuzuweisen! ;)

Sie müssen daran denken, MVVM nicht zu missbrauchen. Ansichten müssen ganz einfach sein! Während ViewModels wiederverwendbar sein sollten! Aus diesem Grund ist es sehr wichtig, View (UITableViewCell) und ViewModel im Controller zu binden.


1
Ja, mein Indexpfad "taktische Korrektur" (den ich nicht empfohlen habe, sondern nur die bescheidenste Bearbeitung zur Behebung des OP-Problems war) leidet unter diesem Problem (jedoch nur, wenn in der Tabellenansicht weiterhin Zeilen hinzugefügt / gelöscht werden). Und wenn sich dieses Phänomen manifestiert, könnte ich das auf andere Weise beheben (anstatt mit demselben Indexpfad nachzuschlagen, fragen Sie einfach das Modell nach der entsprechenden Zeile ab). Aber diese taktische Lösung hat noch größere Probleme (die ich oben beschreibe) als die, die Sie hier ansprechen. Wenn Sie die von UIImageViewmir empfohlene Kategorielösung verwenden, gibt es kein solches Problem in Bezug auf Indexpfade.
Rob

2
Ich mag ein bisschen pedantisch klingen, aber das Aufrufen einer Logik aus VIEW missbraucht diese Architektur.
Badeleux

3

In meinem Fall lag es nicht am Image-Caching (Used SDWebImage). Dies lag an der Nichtübereinstimmung der Tags der benutzerdefinierten Zelle mit indexPath.row.

Auf cellForRowAtIndexPath:

1) Weisen Sie Ihrer benutzerdefinierten Zelle einen Indexwert zu. Zum Beispiel,

cell.tag = indexPath.row

2) Überprüfen Sie im Hauptthread vor dem Zuweisen des Bildes, ob das Bild zur entsprechenden Zelle gehört, indem Sie es mit dem Tag abgleichen.

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

2

Vielen Dank "Rob" ... Ich hatte das gleiche Problem mit UICollectionView und Ihre Antwort hilft mir, mein Problem zu lösen. Hier ist mein 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"];
    }

Für mich mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];ist nie null, also hat das keine Wirkung.
Carbokation

1
Sie können überprüfen, ob Ihre Zelle sichtbar ist oder nicht, indem Sie: for (mycell * updateCell in collectionView.visibleCells) {cellVisible = YES; } if (cellVisible) {cell.coverImageView.imageURL = [NSURL URLWithString: [Dict valueForKey: @ "ImageURL"]]; } Es funktioniert auch für mich
sneha

@sneha Ja, Sie können überprüfen, ob es sichtbar ist, indem Sie dies durchlaufen visibleCells, aber ich vermute, dass die Verwendung [collectionView cellForItemAtIndexPath:indexPath]effizienter ist (und deshalb führen Sie diesen Aufruf überhaupt aus).
Rob

@sneha Übrigens überprüfen Sie in Ihrem Codebeispiel in dieser Antwort oben, ob dies nicht der Fall updateCellist nil, aber Sie verwenden es dann nicht. Sie sollten es nicht nur verwenden, um festzustellen, ob die Sammlungsansichtszelle noch sichtbar ist, sondern Sie sollten es auch updateCellinnerhalb dieses Blocks verwenden cell(was möglicherweise nicht mehr gültig ist). Und wenn nilja, müssen Sie natürlich nichts tun (weil diese Zelle nicht sichtbar ist).
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

Ich denke, Sie möchten das Laden von Zellen zum Zeitpunkt des Ladens von Bildern für Zellen im Hintergrund beschleunigen. Dafür haben wir folgende Schritte ausgeführt:

  1. Überprüfen, ob die Datei im Dokumentverzeichnis vorhanden ist oder nicht.

  2. Wenn nicht, laden Sie das Bild zum ersten Mal und speichern Sie es in unserem Telefon-Dokumentverzeichnis. Wenn Sie das Bild nicht im Telefon speichern möchten, können Sie Zellenbilder direkt in den Hintergrund laden.

  3. Nun der Ladevorgang:

Fügen Sie einfach hinzu: #import "ManabImageOperations.h"

Der Code für eine Zelle lautet wie folgt:

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

Bitte überprüfen Sie die Antwort und kommentieren Sie, wenn ein Problem auftritt ....


0

Einfach ändern,

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

Sie können einfach Ihre URL übergeben,

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

Darf ich den Grund erfahren?
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
Code-Dumps ohne Erklärung sind selten hilfreich. Bitte überlegen Sie, diese Antwort zu bearbeiten, um einen Kontext bereitzustellen.
Chris
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.