13

SDWebImage ライブラリを使用して、作成したカスタム セル クラスを使用するテーブル ビューにリモート イメージをロードしています。私は単に使用します

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

cellForRowAtIndexPath: 問題は、表示されているセルにのみ画像をロードし、オフスクリーンのセルをロードするために上下にスクロールする必要があることではありません。テーブル ビューをスクロールせずにすべての画像を読み込む方法はありますか。前もって感謝します!!

4

4 に答える 4

20

行をプリフェッチする場合は、メソッドに応答して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、次のようにすることができます。

  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デリゲート メソッドがあればいいのにと思います)。以下のルーチンが冗長なリクエストを作成しないことを確認するだけです。

  2. とにかく、たとえば、次の 10 枚の画像を探すプリフェッチ ルーチンを作成します。

    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];
            }
        }];
    }
    
  3. ダウンロード ルーチンでは、イメージのダウンロードが開始されたかどうかを確認し、開始されていない場合は開始します。でこれを行うために、クラス (テーブルの個々の行をサポートするモデル クラス)で Web イメージ操作へのポインターSDWebImageを保持します。weakTableModelRow

    @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 の単純なリストであり、もう一度実行すると、「キューに追加する」などの概念なしで前のリストをキャンセルします)。それは有望な概念ですが、実行には少し弱いです。そして試してみると、UXが著しく低下しました。

したがって、私はSDWebImagePrefetcher(少なくとも改善されるまでは) 使用しない傾向があり、初歩的なプリフェッチ手法に固執します。それほど洗練されたものではありませんが、機能しているようです。

于 2013-03-29T20:09:27.370 に答える
1

これは一例であり、目的に合わせて実装する必要があります。
あなたの 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ユーザーがテーブルビューをスクロールしている間にダウンロードされた画像の代わりに、このセルを別の画像に再利用するかどうかを検出する必要がありました。

于 2013-03-29T12:17:38.947 に答える
0

同じ問題に遭遇しました。新しいダウンロードが来ると、UIImageView+WebCache が前回のダウンロードをキャンセルすることがわかりました。

これが作者の意図であるかどうかはわかりません。そこでcategory、SDWebImage をベースに UIImageView を新規作成します。

使いやすい:

[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) {

                         }];
于 2016-04-21T01:43:14.423 に答える