2013-03-29 30 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) { 

         }]; 
相关问题