ios - Download asincrono di immagini per UITableView con GCD




objective-c ios6 (5)

Hai pensato di usare SDWebImage? Scarica anche l'immagine dall'URL specificato in modo asincrono. Non ho usato l'intera libreria (importato solo UIImageView + WebCache.h). Una volta importato, tutto ciò che devi fare è chiamare il metodo in quanto tale:

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

Potrebbe essere eccessivo se si utilizza AFNetworking 2.0, ma ha funzionato per me.

Ecco il link al github se vuoi provarlo

Sto scaricando le immagini per la mia vista utente in modo asincrono tramite GCD, ma c'è un problema: quando si sfogliano immagini sfarfallio e si cambia tutto il tempo. Ho provato a impostare l'immagine a zero con ogni cella, ma non aiuta molto. Quando si scorre velocemente il backup, tutte le immagini sono errate. Cosa posso fare a riguardo? Ecco il mio metodo per le cellule:

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

Ho studiato questo problema e ho trovato un approccio eccellente personalizzando un UITableViewCell.

#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

Ora, nel TableViewController, dichiarare due proprietà per NSURLSessionConfiguration e NSURLSession e inizializzarle 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];
}

.
.
.

Supponiamo che l'origine dei dati sia un array NSMutableDictionary (o NSManagedObjectContext). È possibile scaricare facilmente l'immagine per ogni cella, con la memorizzazione nella cache, in questo modo:

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

Spero che aiuti alcuni sviluppatori! Saluti.

CREDITS: Immagini vista tabella in iOS 7


Il punto è che non hai compreso appieno il concetto di riutilizzo delle celle. Ciò non concorda molto bene con i download asincroni.

Il blocco

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

viene eseguito quando la richiesta è finita e tutti i dati sono stati caricati. Ma la cella ottiene il suo valore quando viene creato il blocco.

Nel momento in cui viene eseguito il blocco, la cella punta ancora a una delle celle esistenti. Ma è molto probabile che l'utente continui a scorrere. L'oggetto cella è stato riutilizzato nel frattempo e l'immagine è associata a una cella " vecchia " che viene riutilizzata, assegnata e visualizzata. Poco dopo viene caricata, assegnata e visualizzata l'immagine corretta, a meno che l'utente non abbia eseguito lo scrolling ulteriore. e così via e così via.

Dovresti cercare un modo più intelligente per farlo. Ci sono molti turori. Google per il caricamento delle immagini pigro.


Non reinventare la ruota, usa solo questo paio di fantastici pod:

https://github.com/rs/SDWebImage

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

Semplice come:

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

Usa il percorso dell'indice per ottenere la cella. Se non è visibile, la cella sarà nil e non avrai problemi. Ovviamente probabilmente vorrai memorizzare nella cache i dati quando vengono scaricati, in modo da impostare immediatamente l'immagine della cella quando hai già l'immagine.

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




ios6