SDWebImage在滚动之前不会加载远程图像

我正在使用SDWebImage库将远程图像加载到使用我创build的自定义单元类的表视图中。 我只是使用

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

在cellForRowAtIndexPath:现在的问题是它只在可见的单元格加载图像,而不是在屏幕外的单元格,我必须上下滚动,使他们加载。 有什么办法可以加载所有图像,而不必滚动表视图。 提前致谢!!

如果要预取行,可以响应UIScrollViewDelegate方法来确定表滚动何时完成,从而触发预取行。 你可以使用SDWebImagePrefetcher来执行预取(在我原来的答案中,我对这个有用的类有点不屑一顾,但现在看起来工作得相当好):

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

这里是简单的cellForRowAtIndexPath (不完全相关,但只是显示,如果你使用SDWebImagePrefetcher ,你不必乱七八糟的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; } 

这些UIScrollViewDelegate方法在滚动完成时预取更多行

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

你显然需要实现一个预取例程。 这将获取可见单元格两侧单元格的NSIndexPath值,获取它们的图像URL,然后预取该数据。

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

这些是用于获取紧邻可见单元格之前的行的NSIndexPath以及紧跟在可见单元格之后的实用程序的方法:

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

这里有很多,但实际上, SDWebImageSDWebImagePrefetcher正在进行繁重的工作。

为了完整起见,我在下面列出了我原来的答案。


原始答案:

如果你想用SDWebImage做一些预取,你可以做如下的事情:

  1. 添加一个完成块到你的setImageWithURL调用:

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

    我必须承认我不太喜欢在这里调用我的prefetcher例程(我希望iOS有一些很好的didFinishTableRefresh委托方法),但它可以工作,即使它比我真正想要的更多地调用例程。 我只是确保下面的例程确保它不会提出多余的请求。

  2. 无论如何,我写一个预取例程,查找接下来的十个图像:

     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. 在下载例程中,您可以检查图像下载是否已经开始,如果没有,则启动它。 要做到这一点与SDWebImage ,我在我的TableModelRow类(支持我的表中的单个行的模型类)保持一个weak指针,以web图像操作:

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

    然后我有downloadImageIfNeeded例程开始下载,如果它还没有(你可以看到为什么使这个weak是如此重要的…我检查,看看这一行已经有一个未决的操作之前开始另一个)。 我没有做任何与下载的图像(为了debugging的目的,logging事实,下载已完成),而只是下载并让SDImageWeb跟踪我caching的图像,所以当cellForRowAtIndexPath后来请求当用户向下滚动时,图像就在那里,准备就绪并等待。

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

    我的一部分人认为,首先调用imageManager.imageCache实例方法queryDiskCacheForKey可能更健壮,但在做了一些testing之后,看起来并不像需要的那样(而且downloadWithURL对我们来说也是如此)。

我应该指出, SDImageWeb库有一个SDWebImagePrefetcher类(请参阅文档 )。 这个类的名字是非常有希望的,但是看代码,所有的都尊重一个优秀的库,这对我来说并不是很健壮(例如,它是一个简单的URL列表,如果你再次执行,它取消了之前的列表,没有“添加到队列”或类似的东西)的概念。 这是一个很有希望的概念,但执行力有点弱。 当我尝试时,我的用户体验显着受损。

所以,我倾向于不使用SDWebImagePrefetcher (至less在改进之前),并坚持我的基本预取技术。 这不是非常复杂,但它似乎工作。

我只需要解决这个确切的问题,不希望预取程序的开销。 必须有一些额外的内部的东西发生与内置的imageView属性,防止加载,因为一个新的UIImageView工作得很好。

如果你不介意(或已经)使用UITableViewCell的子类,我的解决scheme是相当干净的:

  1. 子类UITableViewCell。
  2. 在你的子类中,隐藏self.imageView。
  3. 创build你自己的UIImageView子视图并设置这个视图的图像。

这里是我自己的代码的修改版本(这里没有文档设置框架,以匹配的iOS照片应用程序的专辑封面的大小和位置):

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

这是一个例子,你需要实现这个目的。
你的UITableView委托:

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

您的自定义UITableViewCell .h文件

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

您的自定义UITableViewCell .m文件

 // ... 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需要检测我们是否将该单元格用于另一个图像,而不是在用户滚动表格视图时下载的图像。

我遇到了同样的问题,我发现UIImageView + WebCache取消了最后一次下载时的新下载。

我不确定这是否是作者的意图。 所以我写了一个基于SDWebImage的新的UIImageView category

使用方便:

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

要查看更多: ImageDownloadGroup

高级用法:

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