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; }
这里有很多,但实际上, SDWebImage
和SDWebImagePrefetcher
正在进行繁重的工作。
为了完整起见,我在下面列出了我原来的答案。
原始答案:
如果你想用SDWebImage
做一些预取,你可以做如下的事情:
-
添加一个完成块到你的
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
委托方法),但它可以工作,即使它比我真正想要的更多地调用例程。 我只是确保下面的例程确保它不会提出多余的请求。 -
无论如何,我写一个预取例程,查找接下来的十个图像:
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]; } }]; }
-
在下载例程中,您可以检查图像下载是否已经开始,如果没有,则启动它。 要做到这一点与
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是相当干净的:
- 子类UITableViewCell。
- 在你的子类中,隐藏self.imageView。
- 创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) { }];