2013-03-29 34 views
12

我正在使用SDWebImage庫將遠程圖像加載到使用我創建的自定義單元類的表視圖中。我簡單地使用SDWebImage不會加載遠程圖像,直到滾動

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

: 現在的問題是,它加載圖像中可見單元格,而不是爲了那些屏幕之外細胞,我必須上下滾動,使其加載。有什麼辦法可以加載所有圖像,而無需滾動表格視圖。 在此先感謝!

+0

你確定這些細胞正在創建這些屏幕之外?你知道UITableView的行爲嗎? – Exploring

+0

細胞正在被重新使用 – user2082760

+0

是的,那麼哪些細胞在屏幕外,那麼將爲那些細胞調用實例方法?這不是SDWebImage的問題。 – Exploring

回答

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方法滾動完成

當預取更多的行

你顯然需要實現一個預取例程。這將獲取可見單元格每邊的單元格的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. 反正我寫的預取程序,看起來,比如說,未來十年圖片:

    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. 在下載程序中,你可以檢查,看看是否已開始下載圖片,如果不,然後啓動它。與SDWebImage做到這一點,我保持weak指向Web圖像操作我TableModelRow類(即備份我的表中的各行的模型類):

    @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類(見the documentation)。這個類的名字是非常有前途的,但是看着代碼,所有人都尊重一個優秀的庫,這對我來說並不是很健壯(例如,它是一個簡單的URL提取列表,如果你再次執行,它取消了之前的列表,沒有「添加到隊列」或類似的概念)。這是一個很有希望的概念,但執行力有點弱。當我嘗試它時,我的用戶體驗顯着受損。因此,我傾向於不使用SDWebImagePrefetcher(至少在改進之前),並堅持我的基本預取技術。這不是非常複雜,但它似乎工作。

+0

哇..那真是太棒了羅伯..噸一噸! – user2082760

+1

感謝這個偉大的答案。我建議稍微改進一下這個答案,例如蘋果如何在他們的例子中進行延遲加載。 [鏈接](https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html)。爲什麼不使用' - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(布爾)減速'和' - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView ' –

+0

@AnilPuttabuddhi同意,你可以觸發這與'UIScrollViewDelegate '方法。我已經更新了我的答案,不僅如此,還使用了'SDWebImagePrefetcher'。我相信這個演繹也能更好地處理多個部分。 – Rob

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 

需要檢測是否將此單元格用於其他圖像,而不是在用戶滾動表格視圖時下載的圖像。

2

我只是不得不解決這個確切的問題,並不希望預取程序的開銷。使用內置的imageView屬性必須有一些額外的底層操作,以防止加載,因爲新的UIImageView可以很好地工作。

我的解決方案是很乾淨,如果你不使用的UITableViewCell的子類介意(或已經):

  1. 子類的UITableViewCell。
  2. 在你的子類中,隱藏self.imageView。
  3. 創建您自己的UIImageView子視圖並設置視圖的圖像。

下面是我自己的代碼修改後的版本(這裏沒有證件被設置在框架匹配的iOS照片應用的專輯的大小&位置覆蓋):

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

我遇到了同樣的問題,我發現的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) { 

         }]; 
相關問題