2013-03-27 14 views
30

Ich Bilder für meine uitableview asynchron mit GCD herunterladen, aber es gibt ein Problem - beim Bildlauf flackern und ändern die ganze Zeit. Ich habe versucht, Bild mit jeder Zelle auf Null zu setzen, aber es hilft nicht viel. Beim schnellen Zurückscrollen sind alle Bilder falsch. Was kann ich dagegen tun? Hier ist meine Methode für Zellen:Asynchrones Herunterladen von Bildern für UITableView mit GCD

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

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 

       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 

       UIImage* image = [[UIImage alloc] initWithData:imageData]; 

       dispatch_async(dispatch_get_main_queue(), ^{ 
        cell.imageView.image = image; 
        [cell setNeedsLayout]; 
        }); 
      }); 

    cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 
    return cell; 
} 

Antwort

89

Das Problem hierbei ist, dass Ihre Bild-Abrufen Blöcke Verweise auf die Tableview Zellen halten. Wenn der Download abgeschlossen ist, wird die Eigenschaft imageView.image festgelegt, selbst wenn Sie die Zelle recycelt haben, um eine andere Zeile anzuzeigen.

Sie benötigen Ihren Download-Abschlussblock, um zu testen, ob das Bild noch für die Zelle relevant ist, bevor das Bild festgelegt wird.

Es ist auch erwähnenswert, dass Sie die Bilder nicht irgendwo anders als in der Zelle speichern, so dass Sie sie jedes Mal erneut herunterladen, wenn Sie eine Zeile auf dem Bildschirm scrollen. Wahrscheinlich möchten Sie sie irgendwo zwischenspeichern und nach lokal zwischengespeicherten Bildern suchen, bevor Sie einen Download starten.

Edit: hier ist eine einfache Möglichkeit zu testen, die tag Eigenschaft der Zelle mit:

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

    cell.tag = indexPath.row; 
    NSDictionary *parsedData = self.loader.parsedData[indexPath.row]; 
    if (parsedData) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
     dispatch_async(queue, ^(void) { 

      NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]]; 

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

     cell.textLabel.text = parsedData[@"id"]; 
    } 
    return cell; 
} 
+0

@SeamusCampbell, konnte nicht 'indexPath.row' das gleiche gilt für die wiederverwendet Zelle sein, wenn es in einem anderen Abschnitt ist? In diesem Fall würde der Tag-Check unpassend verlaufen. –

+0

Ja, der obige Code geht von einer einteiligen Tabelle aus. –

+0

@SeamusCampbell, irgendwelche Gedanken für eine Multi-Section-Tabelle :) –

7

Der Punkt ist, dass Sie nicht vollständig die Zelle Wiederverwendung Konzept verstanden. Das stimmt bei asynchronen Downloads nicht sehr gut überein.

Der Block

^{ 
    cell.imageView.image = image; 
    [cell setNeedsLayout]; 
} 

ausgeführt wird, wenn die Anforderung geladen ist fertig und wurde alle Daten. Aber Zelle erhält ihren Wert, wenn der Block erstellt wird.

Zu dem Zeitpunkt, zu dem der Block ausgeführt wird, zeigt die Zelle immer noch auf eine der vorhandenen Zellen. Es ist jedoch sehr wahrscheinlich, dass der Benutzer weiter scrollte. Das Zellenobjekt wurde zwischenzeitlich wiederverwendet, und das Bild ist mit einer 'alten'-Zelle verknüpft, die wiederverwendet und zugewiesen und angezeigt wird. Kurz darauf wird das richtige Bild geladen und zugeordnet und angezeigt, sofern der Benutzer nicht weiter gescrollt hat. und so weiter und so weiter.

Sie sollten nach einer klügeren Methode suchen, das zu tun. Es gibt viele Turorien. Google für das faule Laden von Bildern.

6

Verwenden Sie den Indexpfad, um die Zelle abzurufen. Wenn es nicht sichtbar ist, wird die Zelle nil sein und Sie werden kein Problem haben. Natürlich möchten Sie die Daten beim Herunterladen im Cache speichern, um das Bild der Zelle sofort zu setzen, wenn Sie das Bild bereits haben.

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

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 
       // You may want to cache this explicitly instead of reloading every time. 
       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 
       UIImage* image = [[UIImage alloc] initWithData:imageData]; 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        // Capture the indexPath variable, not the cell variable, and use that 
        UITableViewCell *blockCell = [tableView cellForRowAtIndexPath:indexPath]; 
        blockCell.imageView.image = image; 
        [blockCell setNeedsLayout]; 
       }); 
      }); 
     cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 

    return cell; 
} 
+0

Nur eine Anmerkung, das funktioniert nicht mit sehr schnellem Scrollen. Es ist immer noch möglich, ein Bild in einer falschen Zelle zu sehen. Wenn Sie 'tableView cellForRowAtIndexPath: indexPath] direkt vor der Wiederverwendung der Zelle aufrufen, wird der Aufruf von 'blockCell.imageView.image = image' sehr wahrscheinlich für die Zelle aufgerufen, nachdem sie erneut verwendet wurde. –

+0

@AndrewRobinson, wenn es nicht mit schnellem Scrollen funktioniert, dann funktioniert es nicht. Aber ich habe nicht gesehen, dass dieser Ansatz in der Art scheitert, wie Sie ihn beschreiben - könnten Sie näher ausführen, wie Sie festgestellt haben, dass die Zelle während der Ausführung des Blocks für einen anderen Indexpfad wiederverwendet wurde? –

+0

@AndrewRobinson wenn ich ein Beispielprojekt auf GitHub posten könnte oder etwas, mit dem ich spielen könnte, würde ich gerne auch untersuchen; abgesehen von dem Blick auf Ihr Projekt, obwohl ich leider keinen Einblick von meinem Kopf habe. –

1

Haben Sie an SDWebImage gedacht? Es lädt das Bild von der gegebenen URL auch asynchron herunter. Ich habe nicht die gesamte Bibliothek verwendet (nur UIImageView + WebCache.h importiert). Nach dem Import alles, was Sie tun müssen, ist die Methode als solche bezeichnen:

[UIImageXYZ sd_setImageWithURL:["url you're retrieving from"] placeholderImage:[UIImage imageNamed:@"defaultProfile.jpeg"]]; 

Es treibt sein könnte, wenn Sie verwenden AFNetworking 2.0, aber es funktionierte für mich.

Here is the link to the github if you want to give it a try

3

Ich habe über dieses Problem studiert und ich fand einen ausgezeichneten Ansatz durch eine UITableViewCell anpassen.

#import <UIKit/UIKit.h> 

@interface MyCustomCell : UITableViewCell 

@property (nonatomic, strong) NSURLSessionDataTask *imageDownloadTask; 
@property (nonatomic, weak) IBOutlet UIImageView *myImageView; 
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *activityIndicator; 

@end 

nun in Ihrem Tableviewcontroller, erklären zwei Eigenschaften für NSURLSessionConfiguration und NSURLSession und initialisieren sie in ViewDidLoad:

@interface MyTableViewController() 

@property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; 
@property (nonatomic, strong) NSURLSession *session; 
. 
. 
. 
@end 

@implementation TimesVC 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 
    _session = [NSURLSession sessionWithConfiguration:_sessionConfig]; 
} 

. 
. 
. 

Lassen Sie uns das nehme an Ihrer Datenquelle eines Arrays NSMutableDictionary (oder NSManagedObjectContext). Sie können einfach Bild für jede Zelle, mit Caching, wie folgt herunterladen:

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

    if (!cell) 
    { 
     cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault 
           reuseIdentifier:@"cell"]; 
    } 

    NSMutableDictionary *myDictionary = [_myArrayDataSource objectAtIndex:indexPath.row];  

    if (cell.imageDownloadTask) 
    { 
     [cell.imageDownloadTask cancel]; 
    } 

    [cell.activityIndicator startAnimating]; 
    cell.myImageView.image = nil; 

    if (![myDictionary valueForKey:@"image"]) 
    { 
     NSString *urlString = [myDictionary valueForKey:@"imageURL"]; 
     NSURL *imageURL = [NSURL URLWithString:urlString]; 
     if (imageURL) 
     { 
      cell.imageDownloadTask = [_session dataTaskWithURL:imageURL 
       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
      { 
       if (error) 
       { 
        NSLog(@"ERROR: %@", error); 
       } 
       else 
       { 
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 

        if (httpResponse.statusCode == 200) 
        { 
         UIImage *image = [UIImage imageWithData:data]; 

         dispatch_async(dispatch_get_main_queue(), ^{ 
          [myDictionary setValue:data forKey:@"image"]; 
          [cell.myImageView setImage:image]; 
          [cell.activityIndicator stopAnimating]; 
         }); 
        } 
        else 
        { 
         NSLog(@"Couldn't load image at URL: %@", imageURL); 
         NSLog(@"HTTP %d", httpResponse.statusCode); 
        } 
       } 
      }]; 

      [cell.imageDownloadTask resume]; 
     } 
    } 
    else 
    { 
     [cell.myImageView setImage:[UIImage imageWithData:[myDictionary valueForKey:@"image"]]]; 
     [cell.activityIndicator stopAnimating]; 
    } 

    return cell; 
} 

Ich hoffe, es hilft ein paar Entwickler! Prost.

KREDITE: Table View Images in iOS 7

2

das Rad nicht neu erfinden nicht, nur diese paar super Schoten verwenden:

https://github.com/rs/SDWebImage

https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage

So einfach wie:

[self.eventImage setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [SchemeConfiguration APIEndpoint] , _event.imageUrl]] 
          placeholderImage:nil 
           completed:^(UIImage *image, 
              NSError *error, 
              SDImageCacheType cacheType, 
              NSURL *imageURL) 
    { 
     event.image = UIImagePNGRepresentation(image); 
    } usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 
+0

Es war Interview-Constraint, dies ohne Bibliotheken von Drittanbietern zu implementieren. – Dvole

1

Neben akzeptiert a nswer von Seamus Campbell sollte man auch wissen, dass das irgendwann nicht funktioniert. In diesem Fall sollten wir die spezifische Zelle neu laden. So

if (image) { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
      if (cell.tag == indexPath.row) { 
       cell.imageView.image = image; 
       [cell setNeedsLayout]; 
      } 
     }); 
} 

sollte in geändert werden,

if (image) { 
     dispatch_async(dispatch_get_main_queue(), ^{ 
       if (cell.tag == indexPath.row) { 
        cell.imageView.image = image; 
        self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None) 
       } 
      }); 
    } 
Verwandte Themen