防止 UITableViewCell 的 UIImageView 调整异步加载的 UIImage 的大小

Posted

技术标签:

【中文标题】防止 UITableViewCell 的 UIImageView 调整异步加载的 UIImage 的大小【英文标题】:Prevent UITableViewCell's UIImageView from resizing asynchronously loaded UIImage 【发布时间】:2013-08-02 02:55:58 【问题描述】:

我在我的UITableViewCell 中异步加载UIImageViews。我的图像视图是 100 点乘 100 点的正方形,但我检索到的 UIImages 并不总是具有相同的宽度和高度。

我的问题是:在加载单元格时,UIImageViews 都是 100 x 100,这很好,但内部 UIImages 被拉伸到那个大小(左图)。当我按下单元格并突出显示时,图像会突然调整到正确的比例(右图)。此外,当我进一步向下滚动到UITableView 时,所有图像也是 100x100 并被拉伸,但是当我向上滚动再次查看单元格时,它们已经突然调整大小了。不过,我不知道他们遵循的缩放比例。

我做错了什么?我已经将UIImageViews'contentMode 设置为UIViewContentModeCenter,但正如您所见,它不起作用。请帮忙?我的完整代码如下。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"searchResultCell"];
    if (!cell) 
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"searchResultCell"];
    

    // Get the Ad object that will be displayed in this cell.
    Ad *ad = (Ad *)[self.searchResults objectAtIndex:indexPath.row];

    // Set up the detail text on the right.
    cell.textLabel.attributedText = ad.attributedTextDisplay;
    cell.textLabel.numberOfLines = 0;
    cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;

    // Set up the image on the left.
    NSString *thumbnailURL = ad.thumbnailURL;
    NSData *data = [FTWCache objectForKey:[MD5 hash:thumbnailURL]];

    // If the image has been previously downloaded and is stored in FTWCache.
    if (data) 
        // Then use that data instead.
        cell.imageView.image = [UIImage imageWithData:data];
     else 
        // Assign default image before beginning background image retrieval task.
        cell.imageView.image = [UIImage imageNamed:@"image_stub_ad"];

        // Trigger background task that retrieves the actual thumbnail from the URL.
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
            NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:thumbnailURL]];

            // Proceed only with updating the table view cell if the returned NSData is not nil.
            if (imageData) 
                dispatch_sync(dispatch_get_main_queue(), ^
                    // UPDATED: Added call to setNeedsDisplay.
                    UITableViewCell *theSameCell = [tableView cellForRowAtIndexPath:indexPath];
                    theSameCell.imageView.image = [UIImage imageWithData:imageData];
                    [theSameCell setNeedsDisplay];
                );
            
        );
    

    // UPDATED: Set the contentMode to UIViewContentModeScaleAspectFit instead of UIViewContentModeCenter.
    cell.imageView.contentMode = UIViewContentModeScaleAspectFit;

    return cell;

【问题讨论】:

在您下面的声明中“// 将 contentMode 设置为 ...”:[tableView cellForRowAtIndexPath].imageView.contentMode = UIViewContentModeCenter; 单元格可能仍然是 nil - 这是委托的全部目的:返回一个单元格该索引路径。您可以将其更改为:cell.imageView.contentMode = UIViewContentModeCenter; 而不是这个 [tableView cellForRowAtIndexPath:indexPath].imageView.contentMode = UIViewContentModeCenter; 使用这个 cell.imageView.contentMode = UIViewContentModeScaleAspectFit; ^谢谢你们两个,我就是这么做的。但是,当图像缺少宽度时,它们仍然会在突出显示/滚动时向左移动。但是,对于缺少高度的图像,情况并非如此,只要它们的宽度可以填满整个 100 x 100 区域。我现在要拔头发了…… 一个问题是[tableView cellForRowAtIndexPath:indexPath]返回的nil单元格,另一个与视图模式有关。但是,从远程获取图像数据后,您必须在完成处理程序中调用 [tableView cellForRowAtIndexPath:indexPath] cell.imageView.image = [UIImage imageWithData:imageData]; 【参考方案1】:

不是一个完整的答案,而是在异步任务中触发远程获取的“改进”:

    // Trigger background task that retrieves the actual thumbnail from the URL.
    [NSURLConnection sendAsynchronousRequest:request 
                                       queue:[NSOperationQueue mainQueue] 
                           completionHandler:^(NSURLResponse* response, 
                                               NSData* imageData, 
                                               NSError* error) 
         if (error) 
              [self handleError:error];  // connection failed
              return;
         
         // here, at a minimum, check status code and Content-Type:
         // ... add code
         if (statusCode == 200 && contentTypeOK)  // "OK"
         
             [FTWCache setObject:imageData forKey:[MD5 hash:thumbnailURL]];
             [tableView cellForRowAtIndexPath:indexPath].imageView.image = 
               [UIImage imageWithData:imageData];
         
         else 
             // we didn't get an image, possibly other errors (e.g. authentication, etc.)
             NSError* err = ...;  // add code
             [self handleError:err];
             return;
         
    ];

如果需要,此代码无法取消。也没有办法阻止对同一图像启动多个请求。

上述问题与特定用户场景可能会导致应用产生无限数量的正在运行的网络请求 - 这最终会导致应用因内存问题而崩溃。为了改善这一点,我们需要利用 NSURLConnection 实现带委托的异步样式 - 这使我们有机会取消请求,并进行额外的测量以防止多个请求被调用。

编辑:

另外一项改进是将图像数据插入缓存,并在后台任务上创建 UIImage 对象,其完成处理程序更新单元格并在主线程上执行。

【讨论】:

【参考方案2】:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    ImageViewCell *cell = (ImageViewCell*)[tableView dequeueReusableCellWithIdentifier:@"identifier"];

    if (cell==nil)
    
        cell = [[UImageViewCell alloc] init]; //or from whatever way you generate this cell.

        //Manually set this below on each time the cell is newly created.
        cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
    

    //Do the update of your imageView's image here 
    //( this is the update area when your cell is not nil 
    // and could be a reused cell.)

    cell.imageView.image = your_ui_image ;
    [cell.imageView setNeedsDisplay];

    return cell;

【讨论】:

【参考方案3】:

试试这个..

// Proceed only with updating the table view cell if the returned NSData is not nil.
            if (imageData) 
                dispatch_sync(dispatch_get_main_queue(), ^
                   UITableViewCell *imageCell = [tableView cellForRowAtIndexPath:indexPath]

                    imageCell.imageView.image = [UIImage imageWithData:imageData];
                   [imageCell setNeedsDisplay];//this is the line I added only

                    [FTWCache setObject:imageData forKey:[MD5 hash:thumbnailURL]];
                );
            

【讨论】:

谢谢,但这并没有改变任何东西。 :( 我刚刚做到了。在调用setNeedsDisplay 之前,我注释掉了将内容模式设置为UIViewContentModeCenter 并将其设置为UIViewContentModeScaleAspectFit。好消息是它似乎已经停止了拉伸,但是当我突出显示单元格或向下和向上滚动时,如果单元格宽度不足,它们会向左移动。 只是在外面尝试将模式设置为 imageView..我认为这会对您有所帮助。 我把它放回外面,并按照 cmets 的提示使用 cell 而不是 [tableView cellForRowAtIndexPath:...],但是当图像宽度不足时突出显示/滚动时图像仍会向左移动。缺少高度的图像似乎没问题,只要它们的宽度填满整个 100 x 100 区域。

以上是关于防止 UITableViewCell 的 UIImageView 调整异步加载的 UIImage 的大小的主要内容,如果未能解决你的问题,请参考以下文章

防止 CAGradientLayer 在 UITableViewCell 上的 UIImageView 内重叠

防止 UIView 被重新添加到 UITableViewCell

如何防止 UITableViewCell 中的缩进 contentView 而不是 textLabel

防止 NSManagedObject 引用更新 UITableViewCell

如何防止 UITableViewCell 重复和重用?

防止 UITableViewCell 的 UIImageView 调整异步加载的 UIImage 的大小