![](/img/trans.png)
[英]Using SDWebImage for UITableViewCell with optional image URL
[英]Handling download of image using SDWebImage while reusing UITableViewCell
我使用第三方庫SDWebImage為我的UITableView單元下載圖像,在單元格中創建UIImageView並在配置像這樣的單元格時觸發請求。
[imageView setImageWithURL:[NSURL URLWithString:imageUrl] placeholderImage:[UIImage imageNamed:@"default.jpg"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
}];
它的工作正常,但是當我快速滾動時,大多數圖像都沒有完全下載(我可以在查爾斯看到),因為圖像沒有被緩存。 即使我的單元格被重用,如何緩存已發送的請求,以便相同的請求不會多次進行。
請忽略任何拼寫錯誤:)
有效的iOS 10,不再需要我原來答案的手動預取代碼。 只需設置一個prefetchDataSource
。 例如,在Swift 3中:
override func viewDidLoad() {
super.viewDidLoad()
tableView.prefetchDataSource = self
}
然后有一個prefetchRowsAtIndexPaths
,它使用SDWebImagePrefetcher
來獲取行
extension ViewController: UITableViewDataSourcePrefetching {
public func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
let urls = indexPaths.map { baseURL.appendingPathComponent(images[$0.row]) }
SDWebImagePrefetcher.shared().prefetchURLs(urls)
}
}
你可以擁有標准的cellForRowAt
:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let url = baseURL.appendingPathComponent(images[indexPath.row])
cell.imageView?.sd_setImage(with: url, placeholderImage: placeholder)
return cell
}
就個人而言,我更喜歡AlamofireImage。 所以UITableViewDataSourcePrefetching
略有不同
extension ViewController: UITableViewDataSourcePrefetching {
public func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
let requests = indexPaths.map { URLRequest(url: baseURL.appendingPathComponent(images[$0.row])) }
AlamofireImage.ImageDownloader.default.download(requests)
}
}
顯然, cellForRowAt
會使用af_setImage
:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let url = baseURL.appendingPathComponent(images[indexPath.row])
cell.imageView?.af_setImage(withURL: url, placeholderImage: placeholder)
return cell
}
我在下面的原始答案顯示,對於Objective-C,你如何在10之前的iOS版本中做到這一點(我們必須進行自己的預取計算)。
這種取消不再可見的單元格下載的行為恰恰是在快速滾動時保持異步圖像檢索響應的原因,即使連接速度較慢也是如此。 例如,如果您快速向下滾動到tableview的第100個單元格,您實際上不希望該圖像檢索在前面99行(不再可見)的圖像檢索后面積壓。 我建議單獨保留UIImageView
類別,但如果要為可能滾動到的單元格預取圖像,請使用SDWebImagePrefetcher
。
例如,在我調用reloadData
,我還預先獲取當前可見單元格之前和之后的十個單元格的圖像:
[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self prefetchImagesForTableView:self.tableView];
});
同樣,只要我停止滾動,我也會這樣做:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self prefetchImagesForTableView:self.tableView];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate)
[self prefetchImagesForTableView:self.tableView];
}
就我如何預取十個前后單元格而言,我這樣做:
#pragma mark - Prefetch cells
static NSInteger const kPrefetchRowCount = 10;
/** 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 ([minimumIndexPath compare:indexPath] == NSOrderedDescending)
minimumIndexPath = indexPath;
if ([maximumIndexPath compare:indexPath] == NSOrderedAscending)
maximumIndexPath = indexPath;
}
// build array of imageURLs for cells to prefetch
NSMutableArray<NSIndexPath *> *prefetchIndexPaths = [NSMutableArray array];
NSArray<NSIndexPath *> *precedingRows = [self tableView:tableView indexPathsForPrecedingRows:kPrefetchRowCount fromIndexPath:minimumIndexPath];
[prefetchIndexPaths addObjectsFromArray:precedingRows];
NSArray<NSIndexPath *> *followingRows = [self tableView:tableView indexPathsForFollowingRows:kPrefetchRowCount fromIndexPath:maximumIndexPath];
[prefetchIndexPaths addObjectsFromArray:followingRows];
// build array of imageURLs for cells to prefetch (how you get the image URLs will vary based upon your implementation)
NSMutableArray<NSURL *> *urls = [NSMutableArray array];
for (NSIndexPath *indexPath in prefetchIndexPaths) {
NSURL *url = self.objects[indexPath.row].imageURL;
if (url) {
[urls addObject:url];
}
}
// now prefetch
if ([urls count] > 0) {
[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:urls];
}
}
/** 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<NSIndexPath *> *)tableView:(UITableView *)tableView indexPathsForPrecedingRows:(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<NSIndexPath *> *)tableView:(UITableView *)tableView indexPathsForFollowingRows:(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;
}
導致下載被取消的行在UIImageView+WebCache.m
第一行- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock
調用[self cancelCurrentImageLoad];
。 如果你擺脫這一行,下載操作應繼續進行。
此時的問題是,如果單個UIImageView正在等待多個下載完成,則會出現競爭條件。 上次完成的下載可能不一定是最后一次下載,因此您的單元格中可能會出現錯誤的圖像。
有幾種方法可以解決這個問題。 最簡單的可能只是在UIImageView+WebCache
添加另一個關聯對象,只是最后加載的URL。 然后,您可以在完成塊中檢查此URL,並僅在匹配時設置圖像。
我遇到了同樣的問題,我發現UIImageView + WebCache在新下載時取消了上次下載。
我不確定這是否是作者的意圖。 所以我在SDWebImage上編寫了一個新的UIImageView category
。
查看更多: ImageDownloadGroup
使用方便:
[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) {
}];
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.