使用GCD异步下载UITableView的图像

Dvo*_*ole 34 objective-c ios ios6

我正在使用GCD异步下载我的uitableview的图像,但是有一个问题 - 当滚动图像时闪烁并且一直在变化.我尝试将每个单元格的图像设置为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) {

                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;
}
Run Code Online (Sandbox Code Playgroud)

Sea*_*ell 95

这里的问题是你的图像获取块保持对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;
}
Run Code Online (Sandbox Code Playgroud)

  • 使用indexPath检查它是否是同一个单元格的想法并不好.if(cell.tag == indexPath.row){为什么?因为您可以在完全相同的位置重复使用相同的单元格.基本上你不能假设知道细胞将被重用的位置.这里一个很好的解决方案是将图像URL存储在单元格中,并在设置UIImageView之前检查它是否相同. (2认同)

Her*_*ker 7

关键是你没有完全理解细胞重用概念.这与异步下载不太一致.

    ^{
    cell.imageView.image = image;
    [cell setNeedsLayout];
}
Run Code Online (Sandbox Code Playgroud)

在请求完成并加载所有数据时执行.但是在创建块时,单元格会获得其值.

到执行块时,单元仍然指向现有单元之一.但很可能用户继续滚动.在此期间重新使用单元对象,并且图像与重用并分配和显示的" "单元相关联.在此之后不久,除非用户进一步滚动,否则加载并分配和显示正确的图像.等等等等.

你应该寻找一种更聪明的方法.有很多turorials.谷歌为懒人图像加载.


Car*_*zey 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;
}
Run Code Online (Sandbox Code Playgroud)