在滚动之前,SDWebImage不会加载远程图像 [英] SDWebImage does not load remote images until scroll

查看:90
本文介绍了在滚动之前,SDWebImage不会加载远程图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am using SDWebImage library to load remote images into a table view which uses a custom cell class i have created. I simply use

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

in cellForRowAtIndexPath:
现在问题是它只在可见单元格中加载图像而不是对于屏幕外的单元格,我必须向上和向下滚动以使它们加载。有没有办法我可以加载所有图像,而无需滚动表格视图。
提前致谢!!

in cellForRowAtIndexPath: Now the problem is it loads images in the visible cells only and not for cells that are offscreen for which i have to scroll up and down to make them load. Is there any way i can load all images without having to scroll the table view. Thanks in advance!!

推荐答案

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

If you want to prefetch rows, you can respond to UIScrollViewDelegate methods to determine when the table scrolling is done, triggering a prefetch of the rows. You can perform the prefetch using SDWebImagePrefetcher (in my original answer I was a little dismissive of this useful class, but it seems to work relatively well now):

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

Here is the simple cellForRowAtIndexPath (not entirely relevant, but just showing that if you use SDWebImagePrefetcher, you don't have to mess around with 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 方法在滚动完成时预取更多行

These UIScrollViewDelegate methods prefetch more rows when scrolling finishes

- (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,然后预取该数据。

You obviously need to implement a prefetch routine. This gets the NSIndexPath values for the cells on each side of the visible cells, gets their image URLs, and then prefetches that data.

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

这些是获取<$ c的实用方法$ c> NSIndexPath 用于紧邻可见单元格之前的行以及紧跟可见单元格之后的行:

These are the utility methods for getting the NSIndexPath for the rows immediately preceding the visible cells as well as those immediately following the visible cells:

/** 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 正在进行繁重的工作。

There's a lot there, but in reality, SDWebImage and its SDWebImagePrefetcher is doing the heavy lifting.

我在下面提供我的原始答案为了完整起见。

I include my original answer below for the sake of completeness.

原始答案:

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

If you want to do some prefetching with SDWebImage, you could do something like the following:


  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 委托方法),但它有效,即使它调用例程的次数比我多真的很想要。我只是确定下面的例程确保它不会发出冗余请求。

I must confess I don't really like calling my prefetcher routine here (I wish iOS had some nice didFinishTableRefresh delegate method), but it works, even if it's calling the routine more times than I'd really want. I just make sure below that the routine below makes sure that it won't make redundant requests.

无论如何,我写了一个寻找的预取例程,比如说,接下来的十张图片:

Anyway, I write a prefetch routine that looks for, say, the next ten images:

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 执行此操作,我会在中保留一个指向Web图像操作的指针TableModelRow class(支持我表的各行的模型类):

  • In the downloading routine, you can check to see if the image download has started and, if not, then start it. To do this with SDWebImage, I keep a weak pointer to the web image operation in my TableModelRow class (the model class that backs the individual rows of my table):

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

    然后我有 downloadImageIfNeeded 例程启动一个下载如果它还没有(你可以看到为什么让如此重要......我正在检查这行是否已经有一个操作在启动之前另一个)。我没有对下载的图像做任何事情(为了调试目的,记录下载已完成的事实),而只是下载并让 SDImageWeb 跟踪对于我的缓存图像,所以当 cellForRowAtIndexPath 稍后在用户向下滚动时请求图像,它就在那里,准备好并等待。

    I then have the downloadImageIfNeeded routine start a download if it hasn't already (you can see why making that weak was so important ... I'm checking to see if this row already has an operation pending before starting another). I'm not doing anything with the downloaded image (short of, for debugging purposes, logging the fact that a download was done), but rather just downloading and letting SDImageWeb keep track of the cached image for me, so when cellForRowAtIndexPath later requests the image as the user scrolls down, it's there, ready and waiting.

    - (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 为我们这样做,无论如何)。

    Part of me thinks it might be more robust to call imageManager.imageCache instance method queryDiskCacheForKey first, but after doing some testing, it doesn't look like that's needed (and the downloadWithURL does that for us, anyway).

    我应该指出 SDImageWeb 库确实有一个 SDWebImagePrefetcher 类(参见文档)。这个类的名称是非常有前途的,但是看一下代码,完全依赖于一个优秀的库,这对我来说感觉不太健壮(例如,它是一个简单的URL列表,如果你再次这样做,它取消了先前的列表,没有添加到队列或类似的东西的概念)。这是一个很有希望的概念,但在执行方面有点弱。当我尝试它时,我的用户体验明显受到影响。

    I should point out that the SDImageWeb library does have a SDWebImagePrefetcher class (see the documentation). The name of the class is incredibly promising, but looking at the code, with all deference to an otherwise excellent library, this doesn't feel very robust to me (e.g. it is a simple list of URLs to fetch and if you do it again, it cancels the prior list with no notion of "adding to the queue" or anything like that). It's a promising notion, but a little weak in execution. And when I tried it, my UX suffered noticeably.

    所以,我倾向于不使用 SDWebImagePrefetcher (直到它得到改善,至少),并坚持我的基本预取技术。它不是非常复杂,但似乎有用。

    So, I'm inclined to not use SDWebImagePrefetcher (until it's improved, at least), and stick to my rudimentary prefetching technique. It's not terribly sophisticated, but it seems to work.

    这篇关于在滚动之前,SDWebImage不会加载远程图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆