SDWebImage ne charge pas les images à distance avant de faire défiler

J'utilise la bibliothèque SDWebImage pour charger des images distantes dans une vue de table qui utilise une class de cellule personnalisée que j'ai créée. J'utilise simplement

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

dans cellForRowAtIndexPath: Maintenant le problème est qu'il charge les images dans les cellules visibles seulement et pas pour les cellules qui sont hors écran pour lesquelles je dois faire défiler vers le haut et vers le bas pour les faire charger. Est-il possible de charger toutes les images sans avoir à faire défiler la vue de la table. Merci d'avance!!

Si vous souhaitez pré-charger des lignes, vous pouvez répondre aux methods UIScrollViewDelegate pour déterminer quand le défilement de la table est terminé, déclenchant une prélecture des lignes. Vous pouvez effectuer la prélecture à l'aide de SDWebImagePrefetcher (dans ma réponse initiale, j'étais un peu dédaigneux de cette class utile, mais cela semble fonctionner relativement bien maintenant):

 - (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 URLWithSsortingng: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 

Voici le simple cellForRowAtIndexPath (pas tout à fait pertinent, mais montrant juste que si vous utilisez SDWebImagePrefetcher , vous n'avez pas besoin de déranger avec cellForRowAtIndexPath :

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSSsortingng *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; } 

Ces methods UIScrollViewDelegate prélèvent plusieurs lignes lorsque le défilement se termine

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

Vous devez évidemment implémenter une routine de prélecture. Cela obtient les valeurs NSIndexPath pour les cellules de chaque côté des cellules visibles, obtient leurs URL d'image, puis pré-sélectionne ces données.

 /** 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]; } } 

Ce sont les methods utilitaires pour get le NSIndexPath pour les lignes précédant immédiatement les cellules visibles ainsi que celles qui suivent immédiatement les cellules visibles:

 /** Resortingeve NSIndexPath for a certain number of rows preceding particular NSIndexPath in the table view. * * @param tableView The tableview for which we're going to resortingeve indexPaths. * @param count The number of rows to resortingeve * @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; } /** Resortingeve NSIndexPath for a certain number of following particular NSIndexPath in the table view. * * @param tableView The tableview for which we're going to resortingeve indexPaths. * @param count The number of rows to resortingeve * @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; } 

Il y en a beaucoup, mais en réalité, SDWebImage et son SDWebImagePrefetcher font le gros du travail.

J'inclus ma réponse originale ci-dessous par souci d'exhaustivité.


Réponse originale:

Si vous voulez faire une SDWebImage avec SDWebImage , vous pouvez faire quelque chose comme:

  1. Ajoutez un bloc d'achèvement à votre appel setImageWithURL :

     - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"%s", __FUNCTION__); static NSSsortingng *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; } 

    Je dois avouer que je n'aime pas vraiment appeler ma routine de prefetcher ici (je souhaite que iOS ait une méthode nice didFinishTableRefresh ), mais cela fonctionne, même si elle appelle la routine plus de fois que je le voudrais vraiment. Je m'assure juste ci-dessous que la routine ci-dessous s'assure qu'elle ne fera pas de requests redondantes.

  2. Quoi qu'il en soit, j'écris une routine de prélecture qui cherche, disons, les dix images suivantes:

     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. Dans la routine de téléchargement, vous pouvez vérifier si le téléchargement de l'image a démarré et, dans le cas contraire, le démarrer. Pour ce faire avec SDWebImage , je garde un point weak sur l'opération d'image web dans ma class TableModelRow (la class model qui soutient les lignes individuelles de ma table):

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

    J'ai alors la routine downloadImageIfNeeded démarrer un téléchargement si ce n'est pas déjà fait (vous pouvez voir pourquoi rendre weak était si important … Je vérifie pour voir si cette ligne a déjà une opération en attente avant d'en commencer une autre). Je ne fais rien avec l'image téléchargée (à des fins de debugging, de consignation du fait qu'un téléchargement a été effectué), mais plutôt de download et laisser SDImageWeb garder une trace de l'image caching pour moi, donc quand cellForRowAtIndexPath request plus tard le image que l'user fait défiler vers le bas, il est là, prêt et en attente.

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

    Une partie de moi pense qu'il pourrait être plus robuste d'appeler d' imageManager.imageCache méthode d'instance queryDiskCacheForKey , mais après avoir fait quelques tests, cela ne semble pas nécessaire (et le downloadWithURL fait pour nous, de toute façon).

Je dois souligner que la bibliothèque SDImageWeb possède une class SDWebImagePrefetcher (voir la documentation ). Le nom de la class est incroyablement prometteur, mais en regardant le code, avec toute la déférence pour une bibliothèque par ailleurs excellente, cela ne me semble pas très robuste (par exemple, c'est une simple list d'URL à récupérer et si vous le faites à nouveau , il annule la list précédente sans notion de "l'ajout à la queue" ou quelque chose comme ça). C'est une notion prometteuse, mais un peu faible dans l'exécution. Et quand je l'ai essayé, mon UX a souffert sensiblement.

Donc, je suis enclin à ne pas utiliser SDWebImagePrefetcher (jusqu'à ce qu'il soit au less amélioré), et je m'en tiens à ma technique de préextraction rudimentaire. Ce n'est pas très sophistiqué, mais cela semble fonctionner.

Je devais juste résoudre ce problème et je ne voulais pas les frais généraux du préfet. Il doit y avoir des choses supplémentaires sous le capot qui se passe avec la propriété embeddede imageView qui empêche le chargement, car une nouvelle UIImageView fonctionne très bien.

Ma solution est assez propre si cela ne vous dérange pas (ou utilisez déjà) une sous-class de UITableViewCell:

  1. Sous-class UITableViewCell.
  2. Dans votre sous-class, masquer self.imageView.
  3. Créez votre propre sous-vue UIImageView et définissez l'image de cette vue.

Voici une version modifiée de mon propre code (non documenté ici est de définir le cadre pour correspondre à la taille et la position des couvertures d'albums de l'application iOS Photo):

YourTableCell.h

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

YourTableCell.m

 @implementation YourTableCell @synthesize coverPhoto; - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSSsortingng *)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 NSSsortingng *CellIdentifier = @"Cell"; YourTableCell *cell = (YourTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; //... [cell.coverPhoto setImageWithURL:coverUrl placeholderImage:nil options:SDWebImageCacheMemoryOnly]; //... } 

Ceci est un exemple et vous devez l'implémenter dans votre but.
votre délégué UITableView:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { YourCustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"YourCustomTableViewCellReuseIdentifier"]; if (!cell) { cell = [[[YourCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } NSSsortingng *imageURL = // ... get image url, typically from array [cell loadImageWithURLSsortingng:imageURL forIndexPath:indexPath]; return cell; } 

votre file UITableViewCell .h personnalisé :

 #import <UIKit/UIKit.h> #import "UIImageView+WebCache.h" #import "SDImageCache.h" @interface YourCustomTableViewCell { NSIndexPath *currentLoadingIndexPath; } - (void)loadImageWithURLSsortingng:(NSSsortingng *)urlSsortingng forIndexPath:(NSIndexPath *)indexPath; @end 

votre file UITableViewCell .m personnalisé :

 // ... some other methods - (void)loadImageWithURLSsortingng:(NSSsortingng *)urlSsortingng forIndexPath:(NSIndexPath *)indexPath { currentLoadingIndexPath = indexPath; [self.imageView cancelCurrentImageLoad]; [self.imageView setImage:nil]; NSURL *imageURL = [NSURL URLWithSsortingng:urlSsortingng]; [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 besoin de détecter si nous réutilisons cette cellule pour une autre image au lieu de l'image qui a été téléchargée pendant que l'user fait défiler la vue de la table.

J'ai rencontré le même problème, j'ai trouvé UIImageView + WebCache annuler le dernier téléchargement quand un nouveau téléchargement arrive.

Je ne sais pas si c'est l'intention de l'auteur. J'écris donc une nouvelle category de base UIImageView sur SDWebImage.

Facile à utiliser:

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

Pour voir plus: ImageDownloadGroup

Utilisation avancée:

 // 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) { }];