2013-03-29 7 views
12

Sto utilizzando la libreria SDWebImage per caricare immagini remote in una vista tabella che utilizza una classe di celle personalizzata che ho creato. Io semplicemente usoSDWebImage non carica immagini remote fino a scorrere

[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:@"loading.jpg"]]; 

in cellForRowAtIndexPath: Ora il problema è carica immagini nelle celle visibili e non per le cellule che sono fuori campo per il quale devo scorrere su e giù per farli caricare. C'è un modo in cui posso caricare tutte le immagini senza dover scorrere la vista tabella. Grazie in anticipo !!

+0

Sei sicuro di quelle cellule sono la creazione di che sono fuori dallo schermo? Conosci il comportamento di UITableView? – Exploring

+0

Le celle vengono riutilizzate – user2082760

+0

Sì, quindi quali celle sono disattivate, quindi il metodo di istanza verrà chiamato per quelle celle? Non è il problema di SDWebImage. – Exploring

risposta

20

Se si desidera eseguire il prefetch delle righe, è possibile rispondere ai metodi UIScrollViewDelegate per determinare quando lo scorrimento della tabella è terminato, attivando un prefetch delle righe. È possibile eseguire il precaricamento utilizzando SDWebImagePrefetcher (nella mia risposta iniziale ero un po 'sprezzante di questa classe utile, ma sembra funzionare relativamente bene ora):

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // the details don't really matter here, but the idea is to fetch data, 
    // call `reloadData`, and then prefetch the other images 

    NSURL *url = [NSURL URLWithString:kUrlWithJSONData]; 
    NSURLRequest *request = [NSURLRequest requestWithURL:url]; 
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 
     if (connectionError) { 
      NSLog(@"sendAsynchronousRequest error: %@", connectionError); 
      return; 
     } 

     self.objects = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; 

     [self.tableView reloadData]; 

     [self prefetchImagesForTableView:self.tableView]; 
    }]; 
} 

// some of the basic `UITableViewDataDelegate` methods have been omitted because they're not really relevant 

Ecco la semplice cellForRowAtIndexPath (non del tutto pertinente, ma solo mostrando che se si utilizza SDWebImagePrefetcher, non c'è bisogno di pasticciare con cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *cellIdentifier = @"Cell"; 
    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 
    NSAssert([cell isKindOfClass:[CustomCell class]], @"cell should be CustomCell"); 

    [cell.customImageView setImageWithURL:[self urlForIndexPath:indexPath] placeholderImage:nil]; 
    [cell.customLabel setText:[self textForIndexPath:indexPath]]; 

    return cell; 
} 

Questi metodi UIScrollViewDelegate prefetch più righe durante lo scorrimento finiture

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    // if `decelerate` was true for `scrollViewDidEndDragging:willDecelerate:` 
    // this will be called when the deceleration is done 

    [self prefetchImagesForTableView:self.tableView]; 
} 

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 
{ 
    // if `decelerate` is true, then we shouldn't start prefetching yet, because 
    // `cellForRowAtIndexPath` will be hard at work returning cells for the currently visible 
    // cells. 

    if (!decelerate) 
     [self prefetchImagesForTableView:self.tableView]; 
} 

Ovviamente è necessario implementare una routine di prefetch. Ciò ottiene i valori NSIndexPath per le celle su ciascun lato delle celle visibili, ottiene i relativi URL immagine e quindi esegue il prefetch su tali dati.

/** Prefetch a certain number of images for rows prior to and subsequent to the currently visible cells 
* 
* @param tableView The tableview for which we're going to prefetch images. 
*/ 

- (void)prefetchImagesForTableView:(UITableView *)tableView 
{ 
    NSArray *indexPaths = [self.tableView indexPathsForVisibleRows]; 
    if ([indexPaths count] == 0) return; 

    NSIndexPath *minimumIndexPath = indexPaths[0]; 
    NSIndexPath *maximumIndexPath = [indexPaths lastObject]; 

    // they should be sorted already, but if not, update min and max accordingly 

    for (NSIndexPath *indexPath in indexPaths) 
    { 
     if (indexPath.section < minimumIndexPath.section || (indexPath.section == minimumIndexPath.section && indexPath.row < minimumIndexPath.row)) minimumIndexPath = indexPath; 
     if (indexPath.section > maximumIndexPath.section || (indexPath.section == maximumIndexPath.section && indexPath.row > maximumIndexPath.row)) maximumIndexPath = indexPath; 
    } 

    // build array of imageURLs for cells to prefetch 

    NSMutableArray *imageURLs = [NSMutableArray array]; 
    indexPaths = [self tableView:tableView priorIndexPathCount:kPrefetchRowCount fromIndexPath:minimumIndexPath]; 
    for (NSIndexPath *indexPath in indexPaths) 
     [imageURLs addObject:[self urlForIndexPath:indexPath]]; 
    indexPaths = [self tableView:tableView nextIndexPathCount:kPrefetchRowCount fromIndexPath:maximumIndexPath]; 
    for (NSIndexPath *indexPath in indexPaths) 
     [imageURLs addObject:[self urlForIndexPath:indexPath]]; 

    // now prefetch 

    if ([imageURLs count] > 0) 
    { 
     [[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:imageURLs]; 
    } 
} 

Questi sono i metodi di utilità per ottenere il NSIndexPath per le righe immediatamente precedenti le celle visibili e quelli immediatamente successivi le celle visibili:

/** Retrieve NSIndexPath for a certain number of rows preceding particular NSIndexPath in the table view. 
* 
* @param tableView The tableview for which we're going to retrieve indexPaths. 
* @param count  The number of rows to retrieve 
* @param indexPath The indexPath where we're going to start (presumably the first visible indexPath) 
* 
* @return   An array of indexPaths. 
*/ 

- (NSArray *)tableView:(UITableView *)tableView priorIndexPathCount:(NSInteger)count fromIndexPath:(NSIndexPath *)indexPath 
{ 
    NSMutableArray *indexPaths = [NSMutableArray array]; 
    NSInteger row = indexPath.row; 
    NSInteger section = indexPath.section; 

    for (NSInteger i = 0; i < count; i++) { 
     if (row == 0) { 
      if (section == 0) { 
       return indexPaths; 
      } else { 
       section--; 
       row = [tableView numberOfRowsInSection:section] - 1; 
      } 
     } else { 
      row--; 
     } 
     [indexPaths addObject:[NSIndexPath indexPathForRow:row inSection:section]]; 
    } 

    return indexPaths; 
} 

/** Retrieve NSIndexPath for a certain number of following particular NSIndexPath in the table view. 
* 
* @param tableView The tableview for which we're going to retrieve indexPaths. 
* @param count  The number of rows to retrieve 
* @param indexPath The indexPath where we're going to start (presumably the last visible indexPath) 
* 
* @return   An array of indexPaths. 
*/ 

- (NSArray *)tableView:(UITableView *)tableView nextIndexPathCount:(NSInteger)count fromIndexPath:(NSIndexPath *)indexPath 
{ 
    NSMutableArray *indexPaths = [NSMutableArray array]; 
    NSInteger row = indexPath.row; 
    NSInteger section = indexPath.section; 
    NSInteger rowCountForSection = [tableView numberOfRowsInSection:section]; 

    for (NSInteger i = 0; i < count; i++) { 
     row++; 
     if (row == rowCountForSection) { 
      row = 0; 
      section++; 
      if (section == [tableView numberOfSections]) { 
       return indexPaths; 
      } 
      rowCountForSection = [tableView numberOfRowsInSection:section]; 
     } 
     [indexPaths addObject:[NSIndexPath indexPathForRow:row inSection:section]]; 
    } 

    return indexPaths; 
} 

C'è molto lì, ma in realtà, SDWebImage e il suo SDWebImagePrefetcher sta facendo il sollevamento pesi.

Includo la mia risposta originale di seguito per motivi di completezza.


risposta originale:

Se si vuole fare un po 'prefetching con SDWebImage, si potrebbe fare qualcosa di simile al seguente:

  1. Aggiungere un blocco di completamento al vostro setImageWithURL chiamata:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    { 
        NSLog(@"%s", __FUNCTION__); 
    
        static NSString *cellIdentifier = @"Cell"; 
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 
    
        TableModelRow *rowData = self.objects[indexPath.row]; 
    
        cell.textLabel.text = rowData.title; 
        [cell.imageView setImageWithURL:rowData.url 
            placeholderImage:[UIImage imageNamed:@"placeholder.png"] 
              completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) { 
               [self prefetchImagesForTableView:tableView]; 
              }]; 
    
        return cell; 
    } 
    

    Devo confessare che non mi piace molto chiamare la mia routine prefetcher qui (vorrei che iOS avesse un buon metodo delegato didFinishTableRefresh), ma funziona, anche se chiama la routine più volte di quanto vorrei davvero. Mi limito solo a verificare che la routine seguente assicuri che non crei richieste ridondanti.

  2. Comunque, ho scrivere una routine prefetch che cerca, per esempio, i prossimi dieci immagini:

    const NSInteger kPrefetchRowCount = 10; 
    
    - (void)prefetchImagesForTableView:(UITableView *)tableView 
    { 
        // determine the minimum and maximum visible rows 
    
        NSArray *indexPathsForVisibleRows = [tableView indexPathsForVisibleRows]; 
        NSInteger minimumVisibleRow = [indexPathsForVisibleRows[0] row]; 
        NSInteger maximumVisibleRow = [indexPathsForVisibleRows[0] row]; 
    
        for (NSIndexPath *indexPath in indexPathsForVisibleRows) 
        { 
         if (indexPath.row < minimumVisibleRow) minimumVisibleRow = indexPath.row; 
         if (indexPath.row > maximumVisibleRow) maximumVisibleRow = indexPath.row; 
        } 
    
        // now iterate through our model; 
        // `self.objects` is an array of `TableModelRow` objects, one object 
        // for every row of the table. 
    
        [self.objects enumerateObjectsUsingBlock:^(TableModelRow *obj, NSUInteger idx, BOOL *stop) { 
         NSAssert([obj isKindOfClass:[TableModelRow class]], @"Expected TableModelRow object"); 
    
         // if the index is within `kPrefetchRowCount` rows of our visible rows, let's 
         // fetch the image, if it hasn't already done so. 
    
         if ((idx < minimumVisibleRow && idx >= (minimumVisibleRow - kPrefetchRowCount)) || 
          (idx > maximumVisibleRow && idx <= (maximumVisibleRow + kPrefetchRowCount))) 
         { 
          // my model object has method for initiating a download if needed 
    
          [obj downloadImageIfNeeded]; 
         } 
        }]; 
    } 
    
  3. Nella routine di download, è possibile controllare per vedere se l'immagine download è avviato e, se no, quindi avvialo. Per fare questo con SDWebImage, ho mantenere un puntatore weak per l'operazione di immagine Web nel mio TableModelRow classe (la classe del modello che sostiene le singole righe del mio tavolo):

    @property (nonatomic, weak) id<SDWebImageOperation> webImageOperation; 
    

    Ho poi hanno la downloadImageIfNeeded routine di avviare un download se non lo ha già fatto (puoi capire perché è stato così importante fare weak ... Sto controllando per vedere se questa riga ha già un'operazione in sospeso prima di iniziarne un'altra). Non sto facendo nulla con l'immagine scaricata (a corto di, a scopo di debug, registrando il fatto che è stato fatto un download), ma piuttosto semplicemente scaricando e lasciando che SDImageWeb tenga traccia dell'immagine in cache per me, quindi quando cellForRowAtIndexPath richiede successivamente il immagine mentre l'utente scorre verso il basso, è lì, pronto e in attesa.

    - (void)downloadImageIfNeeded 
    { 
        if (self.webImageOperation) 
         return; 
    
        SDWebImageManager *imageManager = [SDWebImageManager sharedManager]; 
    
        self.webImageOperation = [imageManager downloadWithURL:self.url 
                    options:0 
                    progress:nil 
                   completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) { 
                    NSLog(@"%s: downloaded %@", __FUNCTION__, self.title); 
                    // I'm not going to do anything with the image, but `SDWebImage` has now cached it for me 
                   }]; 
    } 
    

    Una parte di me pensa che potrebbe essere più robusta di chiamare imageManager.imageCache metodo di istanza queryDiskCacheForKey prima, ma dopo aver fatto alcune prove, ma non sembra che è necessario (e la downloadWithURL fa per noi, comunque).

Tengo a precisare che la biblioteca SDImageWeb ha una classe SDWebImagePrefetcher (vedi the documentation). Il nome della classe è incredibilmente promettente, ma guardando il codice, con tutto il rispetto per una libreria altrimenti eccellente, questo non mi sembra molto robusto (ad esempio è un semplice elenco di URL da recuperare e se lo fai di nuovo , annulla la lista precedente senza alcuna nozione di "aggiunta alla coda" o qualcosa del genere). È un'idea promettente, ma un po 'debole nell'esecuzione. E quando l'ho provato, la mia UX ha sofferto notevolmente.

Quindi, sono propenso a non utilizzare SDWebImagePrefetcher (fino a quando non viene migliorato, almeno) e ad attenermi alla mia tecnica di prefetching rudimentale. Non è terribilmente sofisticato, ma sembra funzionare.

+0

wow..che era fantastico Rob..thanx a ton !! – user2082760

+1

Grazie per questa grande risposta. Suggerisco un leggero miglioramento a questa risposta, ad esempio come la mela fa il pigro carico nel loro esempio. [Link] (https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html). Perché non usare '- (void) scrollViewDidEndDragging: (UIScrollView *) scrollView willDecelerate: (BOOL) decelera' and '- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView ' –

+0

@AnilPuttabuddhi D'accordo, puoi attivare questo con 'UIScrollViewDelegate 'metodi. Ho aggiornato la mia risposta con una resa che non solo fa questo, ma impiega anche 'SDWebImagePrefetcher'. Credo che anche questa interpretazione gestisca meglio più sezioni. – Rob

1

Questo è un esempio ed è necessario implementarlo per il proprio scopo.
tua UITableView delegato:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    YourCustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"YourCustomTableViewCellReuseIdentifier"]; 

    if (!cell) 
    { 
     cell = [[[YourCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
           reuseIdentifier:CellIdentifier];   
    } 

    NSString *imageURL = // ... get image url, typically from array 
    [cell loadImageWithURLString:imageURL forIndexPath:indexPath]; 

    return cell; 
} 

vostra abitudine UITableViewCell .h file di:

#import <UIKit/UIKit.h> 
#import "UIImageView+WebCache.h" 
#import "SDImageCache.h" 

@interface YourCustomTableViewCell 
{ 
    NSIndexPath *currentLoadingIndexPath; 
} 

- (void)loadImageWithURLString:(NSString *)urlString forIndexPath:(NSIndexPath *)indexPath; 

@end 

il file .m UITableViewCell personalizzato:

// ... some other methods 

- (void)loadImageWithURLString:(NSString *)urlString forIndexPath:(NSIndexPath *)indexPath 
{ 
    currentLoadingIndexPath = indexPath; 
    [self.imageView cancelCurrentImageLoad]; 
    [self.imageView setImage:nil]; 

    NSURL *imageURL = [NSURL URLWithString:urlString]; 
    [self.imageView setImageWithURL:imageURL 
        placeholderImage:nil 
          options:SDWebImageRetryFailed 
          completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) 
    { 
     if (currentLoadingIndexPath != indexPath) 
     { 
      return; 
     } 

     if (error) 
     { 
      ... // handle error 
     } 
     else 
     { 
      [imageView setImage:image]; 
     } 
    }]; 
} 

// ... some other methods 

currentLoadingIndexPath necessario per rilevare se riutilizziamo questa cella per un'altra immagine anziché l'immagine che è stata scaricata mentre l'utente scorre la vista tabella.

2

Ho dovuto solo risolvere questo problema esatto e non volevo il sovraccarico del prefetcher. Devono esserci alcune cose extra sotto il cofano che si verificano con la proprietà imageView integrata che ne impedisce il caricamento, perché un nuovo UIImageView funziona perfettamente.

La mia soluzione è abbastanza pulito, se non ti dispiace (o sono già) utilizzando una sottoclasse di UITableViewCell:

  1. sottoclasse UITableViewCell.
  2. nella sottoclasse, nascondere self.imageView.
  3. Crea la tua sottoview UIImageView e imposta l'immagine della vista.

Ecco una versione modificata del mio codice (non documentato qui è l'impostazione del telaio in modo che corrisponda alla posizione dimensioni & dell'album di iOS Photo applicazione copre):

YourTableCell.h

@interface YourTableCell : UITableViewCell 
    @property (nonatomic, strong) UIImageView *coverPhoto; 
@end 

YourTableCell.m

@implementation YourTableCell 

@synthesize coverPhoto; 

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 
{ 
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 
    if (self) { 
     self.imageView.image = nil; 
     self.coverPhoto = [[UIImageView alloc] init]; 

     // Any customization, such as initial image, frame bounds, etc. goes here.   

     [self.contentView addSubview:self.coverPhoto]; 
    } 
    return self; 
} 
//... 
@end 

YourTableViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 
    YourTableCell *cell = (YourTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
    //... 
    [cell.coverPhoto setImageWithURL:coverUrl placeholderImage:nil options:SDWebImageCacheMemoryOnly]; 
    //... 
} 
0

Ho incontrato lo stesso problema, ho trovato UIImageView + WebCache annullare ultimo download quando un nuovo download venire.

Non so se questa sia l'intenzione dell'autore. Quindi scrivo un nuovo category di UIImageView base su SDWebImage.

Facile da usare:

[cell.imageView mq_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] 
        groupIdentifier:@"customGroupID" 
         completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { 

         }]; 

Per vedere di più: ImageDownloadGroup

Uso avanzato:

// create customGroup 
MQImageDownloadGroup *customGroup = [[MQImageDownloadGroup alloc] initWithGroupIdentifier:@"tableViewCellGroup"]; 
customGroup.maxConcurrentDownloads = 99; 

// add to MQImageDownloadGroupManage 
[[MQImageDownloadGroupManage shareInstance] addGroup:customGroup]; 

// use download group 
[cell.imageView mq_setImageWithURL:@"https://xxx" 
        groupIdentifier:@"tableViewCellGroup" 
         completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { 

         }];