繁体   English   中英

在重用UITableViewCell时使用SDWebImage处理图像下载

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM