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;
}
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.Antworten:
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.
Der obige Code behebt einige Probleme, die sich aus der Tatsache ergeben, dass die Zelle wiederverwendet wird:
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,
nil
dieimage
Eigenschaft aller Bildansichten oder sonst werden Sie das Flackern der Bilder sehen.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
UITableView
Methode verwendencellForRowAtIndexPath:
(nicht zu verwechseln mit der ähnlich benanntenUITableViewDataSource
MethodetableView:cellForRowAtIndexPath:
), um festzustellen, ob die Zelle für diese Zeile noch sichtbar ist. Diese Methode wird zurückgegeben,nil
wenn 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.
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,
NSURLSession
anstatt sie-[NSData contentsOfURL:]
an eine Hintergrundwarteschlange zu senden.Verwenden Sie
dequeueReusableCellWithIdentifier:forIndexPath:
stattdequeueReusableCellWithIdentifier:
(aber stellen Sie sicher, dass Sie für diese Kennung einen Zellprototyp oder eine Registerklasse oder eine NIB verwenden). undIch habe einen Klassennamen verwendet, der den Namenskonventionen von Cocoa entspricht (dh mit dem Großbuchstaben beginnen).
Trotz dieser Korrekturen gibt es Probleme:
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
NSURLSession
und angebotene ziemlich transparente Caching ermöglichen. Andernfalls stellenNSURLCache
Sie unnötige Serveranforderungen und bieten eine viel langsamere Benutzeroberfläche.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
UIImageView
Kategorie, 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.UIImageView
Dies ist jedoch eine Menge Arbeit, und die oben genannten Kategorien haben dies bereits für Sie erledigt.quelle
updateCell.poster.image = nil
tocell.poster.image = nil;
updateCell wird aufgerufen, bevor es deklariert wird.AFNetworking
ist 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 *)#>];
cellForRowAtIndexPath
zu 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./ * 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:
ODER
Schritt 2 = Verwenden Sie die Methode "dequeueReusableCellWithIdentifier: forIndexPath:" von UITableView wie folgt (hierfür müssen Sie die Klasse oder die Schreibfeder registrieren):
quelle
Es gibt mehrere Frameworks, die dieses Problem lösen. Nur um ein paar zu nennen:
Schnell:
Ziel c:
quelle
SDWebImage
tut dieses Problem lösen. Sie können steuern, wann das Bild heruntergeladen wird, aberSDWebImage
das Bild zuweisen,UIImageView
ohne Sie nach der Erlaubnis dazu zu fragen. Grundsätzlich ist das Problem aus der Frage mit dieser Bibliothek noch nicht gelöst.Swift 3
Ich schreibe meine eigene Light-Implementierung für Image Loader mit NSCache. Kein flackerndes Zellbild!
ImageCacheLoader.swift
Anwendungsbeispiel
quelle
Hier ist die schnelle Version (unter Verwendung des @ Nitesh Borad-Ziel-C-Codes): -
quelle
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.
quelle
UIImageView
mir empfohlene Kategorielösung verwenden, gibt es kein solches Problem in Bezug auf Indexpfade.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,
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.
quelle
Vielen Dank "Rob" ... Ich hatte das gleiche Problem mit UICollectionView und Ihre Antwort hilft mir, mein Problem zu lösen. Hier ist mein Code:
quelle
mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
ist nie null, also hat das keine Wirkung.visibleCells
, aber ich vermute, dass die Verwendung[collectionView cellForItemAtIndexPath:indexPath]
effizienter ist (und deshalb führen Sie diesen Aufruf überhaupt aus).updateCell
istnil
, aber Sie verwenden es dann nicht. Sie sollten es nicht nur verwenden, um festzustellen, ob die Sammlungsansichtszelle noch sichtbar ist, sondern Sie sollten es auchupdateCell
innerhalb dieses Blocks verwendencell
(was möglicherweise nicht mehr gültig ist). Und wennnil
ja, müssen Sie natürlich nichts tun (weil diese Zelle nicht sichtbar ist).quelle
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:
Überprüfen, ob die Datei im Dokumentverzeichnis vorhanden ist oder nicht.
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.
Nun der Ladevorgang:
Fügen Sie einfach hinzu:
#import "ManabImageOperations.h"
Der Code für eine Zelle lautet wie folgt:
ManabImageOperations.h:
ManabImageOperations.m:
Bitte überprüfen Sie die Antwort und kommentieren Sie, wenn ein Problem auftritt ....
quelle
Einfach ändern,
In
quelle
Sie können einfach Ihre URL übergeben,
quelle
quelle