2013-03-27 50 views
30

我正在使用GCD异步下载我的uitableview的图像,但存在一个问题 - 当滚动图像闪烁并且始终更改时。我试图将图像设置为零,但没有太大的帮助。 快速滚动备份时,所有图像都是错误的。我能做些什么? 这里是我的细胞的方法:使用GCD异步下载UITableView的图像

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 

       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 

       UIImage* image = [[UIImage alloc] initWithData:imageData]; 

       dispatch_async(dispatch_get_main_queue(), ^{ 
        cell.imageView.image = image; 
        [cell setNeedsLayout]; 
        }); 
      }); 

    cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 
    return cell; 
} 

回答

89

这里的问题是,你的图像获取块都抱着到的tableview细胞引用。下载完成后,即使您已经回收单元以显示不同的行,它也会设置imageView.image属性。

在设置图像之前,您需要下载完成块来测试图像是否与单元格相关。

另外值得注意的是,您不是将图像存储在单元格以外的任何地方,因此每次在屏幕上滚动一行时都会再次下载它们。在开始下载之前,您可能想将它们缓存在某处并查找本地缓存的图像。

编辑:这里有一个简单的方法来测试,利用细胞tag属性:

- (UITableViewCell *)tableView:(UITableView *)tableView 
     cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

    cell.tag = indexPath.row; 
    NSDictionary *parsedData = self.loader.parsedData[indexPath.row]; 
    if (parsedData) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
     dispatch_async(queue, ^(void) { 

      NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]]; 

      UIImage* image = [[UIImage alloc] initWithData:imageData]; 
      if (image) { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        if (cell.tag == indexPath.row) { 
         cell.imageView.image = image; 
         [cell setNeedsLayout]; 
        } 
       }); 
      } 
     }); 

     cell.textLabel.text = parsedData[@"id"]; 
    } 
    return cell; 
} 
+0

@SeamusCampbell,不能'indexPath.row'是重用电池相同的,如果它是在不同的部分?在这种情况下,标签检查将不适当地通过。 – 2014-02-13 20:16:46

+0

是的,上面的代码假设了一个单节表格。 – 2014-02-13 20:48:56

+0

@SeamusCampbell,对于多节表的任何想法:) – 2014-02-15 20:31:47

7

的一点是,你没有完全了解电池重复使用的概念。这与异步下载不太一致。

^{ 
    cell.imageView.image = image; 
    [cell setNeedsLayout]; 
} 

得到当请求完成后,所有的数据被加载执行的块。但是单元在创建块时会获得它的价值。

在块执行时,单元仍指向其中一个现有单元。但用户很可能继续滚动。在此期间重新使用了单元对象,并且该图像与重用并分配并显示的''单元相关联。不久之后,正确的图像将被加载并分配并显示,除非用户进一步滚动。等等等等。

你应该寻找一个更聪明的方式来做到这一点。有很多turorials。谷歌为懒惰图像加载。

6

使用索引路径获取单元格。如果它不可见,则单元格将为nil,并且您不会遇到问题。当然,您可能需要在下载数据时缓存数据,以便在已经有图像时立即设置单元格的图像。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 
       // You may want to cache this explicitly instead of reloading every time. 
       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 
       UIImage* image = [[UIImage alloc] initWithData:imageData]; 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        // Capture the indexPath variable, not the cell variable, and use that 
        UITableViewCell *blockCell = [tableView cellForRowAtIndexPath:indexPath]; 
        blockCell.imageView.image = image; 
        [blockCell setNeedsLayout]; 
       }); 
      }); 
     cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 

    return cell; 
} 
+0

只需注意,这不适用于非常快速的滚动。仍然可以看到图像添加到不正确的单元格。如果在重用单元之前调用'[tableView cellForRowAtIndexPath:indexPath]',重用单元格后可能会调用'blockCell.imageView.image = image'。 – 2014-02-13 20:05:40

+0

@AndrewRobinson如果它不适用于快速滚动,那么它不起作用。但是,我还没有看到这种方法会以您描述的方式失败,我自己 - 您是否可以详细说明如何确定单元在执行块时是否被重新用于不同的索引路径? – 2014-02-15 15:11:20

+0

@AndrewRobinson如果可以在github上发布一个示例项目或者我可以玩的东西,我也愿意调查;不幸的是,除了看到你的项目之外,我还没有看清楚自己的头脑。 – 2014-02-16 14:21:38

1

您是否想过使用SDWebImage?它也异步地从给定的URL下载图像。我没有使用整个库(仅导入UIImageView + WebCache.h)。导入后,所有你需要做的就是调用该方法,例如:

[UIImageXYZ sd_setImageWithURL:["url you're retrieving from"] placeholderImage:[UIImage imageNamed:@"defaultProfile.jpeg"]]; 

如果你使用AFNetworking 2.0这可能是矫枉过正,但它的工作对我来说。

Here is the link to the github if you want to give it a try

3

我一直在研究这个问题,我发现通过自定义的UITableViewCell的一个很好的办法。现在

#import <UIKit/UIKit.h> 

@interface MyCustomCell : UITableViewCell 

@property (nonatomic, strong) NSURLSessionDataTask *imageDownloadTask; 
@property (nonatomic, weak) IBOutlet UIImageView *myImageView; 
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *activityIndicator; 

@end 

,在你TableViewController,为NSURLSessionConfiguration和NSURLSession声明两个属性和viewDidLoad中初始化它们:

@interface MyTableViewController() 

@property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; 
@property (nonatomic, strong) NSURLSession *session; 
. 
. 
. 
@end 

@implementation TimesVC 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 
    _session = [NSURLSession sessionWithConfiguration:_sessionConfig]; 
} 

. 
. 
. 

让我们supose你的数据源是一个的NSMutableDictionary的阵列(或的NSManagedObjectContext)。您可以下载easilly图像的每个细胞,与缓存,这样的:

. 
. 
. 
- (MyCustomCell *)tableView:(UITableView *)tableView 
    cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 

    if (!cell) 
    { 
     cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault 
           reuseIdentifier:@"cell"]; 
    } 

    NSMutableDictionary *myDictionary = [_myArrayDataSource objectAtIndex:indexPath.row];  

    if (cell.imageDownloadTask) 
    { 
     [cell.imageDownloadTask cancel]; 
    } 

    [cell.activityIndicator startAnimating]; 
    cell.myImageView.image = nil; 

    if (![myDictionary valueForKey:@"image"]) 
    { 
     NSString *urlString = [myDictionary valueForKey:@"imageURL"]; 
     NSURL *imageURL = [NSURL URLWithString:urlString]; 
     if (imageURL) 
     { 
      cell.imageDownloadTask = [_session dataTaskWithURL:imageURL 
       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
      { 
       if (error) 
       { 
        NSLog(@"ERROR: %@", error); 
       } 
       else 
       { 
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 

        if (httpResponse.statusCode == 200) 
        { 
         UIImage *image = [UIImage imageWithData:data]; 

         dispatch_async(dispatch_get_main_queue(), ^{ 
          [myDictionary setValue:data forKey:@"image"]; 
          [cell.myImageView setImage:image]; 
          [cell.activityIndicator stopAnimating]; 
         }); 
        } 
        else 
        { 
         NSLog(@"Couldn't load image at URL: %@", imageURL); 
         NSLog(@"HTTP %d", httpResponse.statusCode); 
        } 
       } 
      }]; 

      [cell.imageDownloadTask resume]; 
     } 
    } 
    else 
    { 
     [cell.myImageView setImage:[UIImage imageWithData:[myDictionary valueForKey:@"image"]]]; 
     [cell.activityIndicator stopAnimating]; 
    } 

    return cell; 
} 

我希望它可以帮助一些开发! 干杯。

鸣谢:Table View Images in iOS 7

2

不要推倒重来,只需使用这对夫妻真棒荚:

https://github.com/rs/SDWebImage

https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage

简单:

[self.eventImage setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [SchemeConfiguration APIEndpoint] , _event.imageUrl]] 
          placeholderImage:nil 
           completed:^(UIImage *image, 
              NSError *error, 
              SDImageCacheType cacheType, 
              NSURL *imageURL) 
    { 
     event.image = UIImagePNGRepresentation(image); 
    } usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 
+0

这是在没有第三方库的情况下实施这个访问约束。 – Dvole 2015-12-18 05:45:47

1

旁边接受了a Seamus Campbell你应该知道,有时这是行不通的。在这种情况下,我们应该重新加载特定的单元格。所以

if (image) { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
      if (cell.tag == indexPath.row) { 
       cell.imageView.image = image; 
       [cell setNeedsLayout]; 
      } 
     }); 
} 

应改为,

if (image) { 
     dispatch_async(dispatch_get_main_queue(), ^{ 
       if (cell.tag == indexPath.row) { 
        cell.imageView.image = image; 
        self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None) 
       } 
      }); 
    }