简体   繁体   English

使用Autolayout在UITableView中异步加载图像

[英]Loading images asynchronously in UITableView with Autolayout

I have a UITableView which loads images asynchronously in a UITableView. 我有一个UITableView,可以在UITableView中异步加载图像。 As all the images are different heights, I set a height constraint according to the height of the image. 由于所有图像都是不同的高度,我根据图像的高度设置高度约束。

This works fine when I use DataWithContentsOfUrl, however freezes the UI. 当我使用DataWithContentsOfUrl时,这可以正常工作,但会冻结UI。 When I load asynchronously, the images pile up on top of each other like so: 当我异步加载时,图像堆叠在一起,如下所示:

http://i.stack.imgur.com/TEUwK.png http://i.stack.imgur.com/TEUwK.png

The code i'm using is as follows: 我正在使用的代码如下:

NSURL * imageURL = [NSURL URLWithString:img];
NSURLRequest* request = [NSURLRequest requestWithURL:imageURL];
cell.imageViewHeightConstraint.constant=0;
[NSURLConnection sendAsynchronousRequest:request    queue:[NSOperationQueue mainQueue]  completionHandler:^(NSURLResponse * response,   NSData * data,  NSError * error) {
    if (!error){
        UIImage *imgz = [UIImage imageWithData:data];
        [cell.CellImg setBackgroundImage:imgz forState:UIControlStateNormal];
        [cell.CellImg sizeToFit];
        cell.imageViewHeightConstraint.constant=result.width*(imgz.size.height/imgz.size.width);
        [cell setNeedsUpdateConstraints];
    }
}];
[cell updateConstraints];

When I scroll up and down a few times the images correctly position themselves. 当我向上和向下滚动几次时,图像正确定位自己。

The question doesn't specify the desired behavior for varying image sizes. 该问题未指定不同图像尺寸的所需行为。 Should the cell get taller to fit, or just the image view (what looks like a button from the code) within the cell? 单元格是否应该更高,或者仅仅是单元格中的图像视图(看起来像代码中的按钮)?

But we should put aside that problem for a moment and work on the more serious problem with the code: it issues an unguarded network request from cellForRowAtIndexPath . 但是我们应暂时搁置这个问题并解决代码中更严重的问题:它从cellForRowAtIndexPath发出无人看守的网络请求。 As a result, (a) a user scrolling back and forth will generate many many redundant requests, and (b) a user scrolling a long way quickly will generate a request that's fulfilled when the cell that started it is gone - reused as the cell for another row. 结果,(a)用户来回滚动将产生许多冗余请求,并且(b)用户快速滚动很长时间将生成在启动它的单元格消失时满足的请求 - 作为单元格重用另一排。

To address (a), the datasource should cache fetched images, and only request those that haven't been received. 为了解决(a),数据源应该缓存提取的图像,并且只请求那些尚未接收的图像。 To address (b), the completion block shouldn't refer directly to the cell. 为了解决(b),完成块不应直接引用该单元。

A simple cache would look like this: 一个简单的缓存看起来像这样:

@property(strong,nonatomic) NSMutableDictionary *images;

// initialize this when you initialize your model
self.images = [@{} mutableCopy];

// move the network code into its own method for clarity
- (void)imageWithPath:(NSString *)path completion:(void (^)(UIImage *, NSError *))completion {
    if (self.images[indexPath]) {
        return completion(self.images[indexPath], nil);
    }
    NSURL *imageURL = [NSURL URLWithString:path];
    NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (!error){
            UIImage *image = [UIImage imageWithData:data];
            self.images[indexPath] = image;
            completion(image, nil);
        } else {
            completion(nil, error);
        }
    }];
}

Now, we fix the multiple request problem by checking first for the image in the cache in cellForRowAtIndexPath. 现在,我们通过首先检查cellForRowAtIndexPath中缓存中的图像来修复多个请求问题。

UIImage *image = self.images[indexPath];
if (image) {
    [cell.CellImg setBackgroundImage:image forState:UIControlStateNormal];
} else {
    // this is a good place for a placeholder image if you want one
    [cell.CellImg setBackgroundImage:nil forState:UIControlStateNormal];
    // presuming that 'img' is a string from your mode
    [self imageWithPath:img completion:^(UIImage *image, NSError *error) {
        // the image is ready, but don't assign it to the cell's subview
        // just reload here, so we get the right cell for the indexPath
        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    }];
}

Also notice what isn't done in the completion block... we're fixing the cell reuse by not referring to the cell. 还要注意完成块中没有完成的操作......我们通过不引用单元格来修复单元重用。 Instead, knowing that the image is now cached, we reload the indexPath. 相反,知道图像现在被缓存,我们重新加载indexPath。

Back to image sizing: most apps like to see the table view cell get taller or shorter along with the variable height subview. 返回图像大小调整:大多数应用程序喜欢看到表格视图单元格随着可变高度子视图而变得更高或更短。 If that's the case, then you should not place a height constraint on that subview at all. 如果是这种情况,那么您根本不应该在该子视图上放置高度约束。 Instead, constrain it's top and bottom edges to the cell's content view (or include it in a chain of subviews who constrain to each other top and bottom and contain the topmost and bottommost subviews top and bottom edge to the cell). 相反,将它的顶部和底部边缘限制在单元格的内容视图中(或将其包含在一个子视图链中,这些子视图将顶部和底部相互约束,并包含单元格的顶部和底部边缘的最顶部和最底部的子视图)。 Then (in iOS 5+, I think), this will allow your cell to change hight with that subview constraint chain... 然后(在iOS 5+中,我认为),这将允许您的单元格改变该子视图约束链...

self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = // your best guess at the average height here

Try the following along with constraint constant: 尝试以下约束常量:

cell.imageViewHeightConstraint.constant=result.width*(imgz.size.height/imgz.size.width);
[table reloadRowsAtIndexPaths:@[indexPathForCurrentCell] withRowAnimation:UITableViewRowAnimationNone];

You can also set the animation. 您也可以设置动画。

Although reloadRowsAtIndexPaths works, it ends up causing the interface to be slow and laggy, especially when the user is scrolling. 虽然reloadRowsAtIndexPaths可以正常工作,但它最终会导致界面缓慢且滞后,尤其是在用户滚动时。 My solution was once the data was loaded to iterate through https://graph.facebook.com/v2.1/%@/?access_token=%@&fields=attachments for the object id, and then store the image height dimensions in an array. 我的解决方案是,一旦加载数据,通过https://graph.facebook.com/v2.1/%@/?access_token=%@&fields=attachments迭代对象id,然后将图像高度尺寸存储在阵列。 Then, in cellForRowAtIndexPath simply setting cell.imageViewHeightConstraint.constant to the value in the array. 然后,在cellForRowAtIndexPath简单地将cell.imageViewHeightConstraint.constant设置为数组中的值。 That way all the cells heights are set as soon as the cell loads, and the image fits into place perfectly as soon as it loads. 这样,所有单元格高度都会在单元格加载后立即设置,并且图像在加载后立即完美匹配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM