从服务器下载图像以显示在 CollectionView 上

Posted

技术标签:

【中文标题】从服务器下载图像以显示在 CollectionView 上【英文标题】:Download Images from a server to display on the CollectionView 【发布时间】:2015-10-28 17:31:08 【问题描述】:

我正在开发一个用户可以出售/购买的产品应用程序。此应用程序基于集合视图。集合视图具有集合单元格,其中显示产品图像缩略图。

以下代码从服务器获取产品图像,并等待下载所有图像,然后将它们显示在单元格中。以下代码有效,但用户等待 10-20 秒才能查看所有产品。有没有更好的处理方式?

- (void)viewDidLoad 
     [super viewDidLoad];
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
     dispatch_async(queue, ^(void) 
        [self loadFromURL];
     dispatch_async(dispatch_get_main_queue(), ^
     );
  );


-(void)loadFromURL 
    NSURL *url = [NSURL URLWithString:@"http://myURL/productAll.php"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = [AFJSONResponseSerializer serializer];

   [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) 
             
      pElements= (NSMutableArray *)responseObject;
      [collectionView reloadData];

      
     failure:^(AFHTTPRequestOperation *operation, NSError *error) 
             
       UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Product" message:[error localizedDescription]delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
     [alertView show];
   ];    
  [operation start];


- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

    return pElements.count;


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

    ProductCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];

    cell.backgroundColor=[UIColor whiteColor];
    NSString *arrayResult = [[pElements objectAtIndex:indexPath.row] objectForKey:@"image"];
    NSData *data = [[NSData alloc] initWithBase64EncodedString:arrayResult options:NSDataBase64DecodingIgnoreUnknownCharacters];
    cell.productImage.image = [[UIImage alloc] initWithData:data];
    cell.productImage.layer.cornerRadius = 10;
    cell.productImage.clipsToBounds = YES;
    return cell;

【问题讨论】:

如果你正在寻找代码审查,你应该在codereview.stackexchange.com询问 实际上,我已经修改了我的问题,我正在尝试找出如何减少用户等待在集合视图上查看产品的时间。 @rmaddy:这看起来不像是代码审查,而是询问如何优化“不完美”代码的不同意见。 @erolyeniaras 这个问题在我发表评论后改变了。 @texas Nice quistain 【参考方案1】:

您有来自服务器的响应,其中所有图像的图像数据都在响应中以 base-64 编码。这意味着响应可能非常大,并且在下载所有内容之前不会显示给用户。

相反,您可以考虑重构您的服务器代码,使其不包含 base-64 格式的图像数据,而是仅包含一个 URL(或某些标识符),以便以后检索图像。您的响应应该小得多,并且应该能够更快地得到处理。

然后,当调用cellForItemAtIndexPath 时,您不会从原始响应中提取图像数据,而是懒惰地(并且异步地)请求单元格的图像。 AFNetworking 在UIImageView+AFNetworking 中提供了一个不错的UIImageView 类别,可以从网络源中异步检索图像。 (在进行异步图像检索时,使用这个类别可以让您摆脱许多微妙的问题。)

顺便说一下,如果您的图片大小不一,您可能希望在原始请求中包含图片的尺寸,以便可以预先调整单元格及其图片视图的大小,而不是将它们的大小调整为检索图像。

--

几个观察:

    您不需要将[self loadFromURL] 分派到后台队列,因为这已经是异步的。我可能会使用请求的GET

    您不能只将responseObject 转换为NSMutableArray,因为它可能是不可变的。如果你真的需要它是可变的,你真的应该使用NSArray或使用mutableCopy

    您正在cellForItemAtIndexPath 中进行一些单元配置。大部分(剪辑、背景颜色等)都可以在 IB 中完成,所以我会在那里完成,而不是通过编程方式完成。您可能需要以编程方式做的唯一事情是圆角(即使这样,我也可能会使用 IBDesignable 子类,尽管这超出了这个问题的范围)。

因此,假设 (a) 您的数组有一个名为 imageURL 的新属性,它是图像的 URL; (b) 单元格具有固定大小的图像视图,您可以执行以下操作:

@interface ViewController ()

@property (nonatomic, strong) AFHTTPRequestOperationManager *manager;
@property (nonatomic, strong) NSArray *pElements;

@end

@implementation ViewController

- (void)viewDidLoad 
    [super viewDidLoad];

    self.manager = [AFHTTPRequestOperationManager manager];

    [self loadFromURL];


-(void)loadFromURL 
    NSString *urlString = @"http://myURL/productAll.php";

    [self.manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) 
        self.pElements = responseObject;
        [self.collectionView reloadData];
     failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) 
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Product" message:[error localizedDescription]delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
        [alertView show];
    ];


- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 
    return self.pElements.count;


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
    ProductCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];

    NSString *imageURL = self.pElements[indexPath.row][@"imageURL"];

    [cell.productImage setImageWithURL:[NSURL URLWithString:imageURL]];
    cell.productImage.layer.cornerRadius = 10;

    return cell;


@end

【讨论】:

非常感谢 Rob,他写了一个非常好的信息丰富的答案。我想知道你能不能说明一点UIIMageView+AFNetworking 我的另一个顾虑是我现在首先获取所有产品,然后将它们显示在集合视图上。有没有更好的方法来处理它?例如,如果从服务器下载任何单个图像,则立即显示它。换句话说,不要等待下载所有图像。 @texas 如果从原始响应中删除base-64编码的数据,然后在cellForItemAtIndexPath中使用UIImageView(AFNetworking)类别(例如setImageWithURL:),那么它将首先获取所有产品(应该相当快),然后在图像进入时显示它们,即它不会等待所有图像下载。如果你用谷歌搜索“AFNetworking UIImageView setImageWithURL tutorial”或类似的东西,你应该会得到很好的点击,比如raywenderlich.com/59255/afnetworking-2-0-tutorial 非常感谢 Rob,感谢您的所有帮助和努力。我已接受并赞成您的回答。我希望许多其他 SOF 用户能从中受益 当然可以,但是您只需要在 Web 服务上实现一些 URL,您就可以为 blob 传递一些标识符,它将返回结果图像。例如,您可能在表中具有该行的唯一标识符,并且您将实现一个新的 PHP 来获取该标识并返回图像。我什至会让 PHP 不对 JSON 中的图像进行 base-64 编码,而是返回实际图像。

以上是关于从服务器下载图像以显示在 CollectionView 上的主要内容,如果未能解决你的问题,请参考以下文章

从服务器下载高分辨率图像以进行视网膜显示的策略

在设备上显示图像时出现问题

如何使用 NSURlConnection 从 Url 下载图像以保存在手机缓存中

如何从服务器下载图像并快速显示淡入淡出动画?

在 tableview 中显示下载的图像

Firebase 存储异步图像下载随机顺序