![](/img/trans.png)
[英]SDWebImage not setting images in UITableView cells until scroll in Xcode 9.0 beta
[英]SDWebImage does not load remote images until scroll
我正在使用SDWebImage庫將遠程圖像加載到一個表視圖中,該視圖使用我創建的自定義單元類。 我只是用
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:@"loading.jpg"]];
in 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
類(支持表的各行的模型類)中保留了一個指向Web圖像操作的weak
指針:
@property (nonatomic, weak) id<SDWebImageOperation> webImageOperation;
然后我將downloadImageIfNeeded
例程開始下載(如果它還沒有)(你可以看到為什么這個weak
是如此重要......我正在檢查這行是否已經有一個操作掛起,然后再啟動另一個)。 我沒有對下載的圖像做任何事情(為了調試目的,記錄下載已完成的事實),而是只是下載並讓SDImageWeb
跟蹤我的緩存圖像,所以當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
可能會更健壯,但在進行一些測試之后,它看起來並不像需要的那樣(而且無論如何, downloadWithURL
都為我們做了這個)。
我應該指出, SDImageWeb
庫確實有一個SDWebImagePrefetcher
類(參見文檔 )。 這個類的名稱是非常有前途的,但看着代碼,完全尊重一個優秀的庫,這對我來說感覺不太健壯(例如,它是一個簡單的URL列表,如果你再次這樣做,它取消了先前的列表,沒有“添加到隊列”或類似的東西的概念)。 這是一個很有希望的概念,但在執行方面有點弱。 當我嘗試它時,我的用戶體驗明顯遭受了損失。
所以,我傾向於不使用SDWebImagePrefetcher
(至少它是改進的),並堅持我的基本預取技術。 它不是非常復雜,但似乎有效。
我只需解決這個確切的問題,並不想要預取器的開銷。 內置的imageView屬性必然會發生一些額外的引擎蓋內容,這會阻止加載,因為新的UIImageView工作得很好。
如果您不介意(或已經)使用UITableViewCell的子類,我的解決方案非常干凈:
這是我自己的代碼的修改版本(這里沒有記錄是設置框架以匹配iOS Photo應用程序專輯封面的大小和位置):
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) {
}];
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.