简体   繁体   English

如何在 UIImageView 中异步加载图像?

[英]How to asynchronously load an image in an UIImageView?

I have an UIView with an UIImageView subview.我有一个带有 UIImageView 子视图的 UIView。 I need to load an image in the UIImageView without blocking the UI.我需要在不阻塞 UI 的情况下在 UIImageView 中加载图像。 The blocking call seems to be: UIImage imageNamed: .阻塞调用似乎是: UIImage imageNamed: Here is what I thought solved this problem:这是我认为解决此问题的方法:

-(void)updateImageViewContent {
    dispatch_async(
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        dispatch_sync(dispatch_get_main_queue(), ^{
            [[self imageView] setImage:img];
        });
    });
}

The image is small (150x100).图像很小(150x100)。

However the UI is still blocked when loading the image.但是,加载图像时 UI 仍然被阻止。 What am I missing ?我错过了什么?

Here is a small code sample that exhibits this behaviour:这是一个展示此行为的小代码示例:

Create a new class based on UIImageView, set its user interaction to YES, add two instances in a UIView, and implement its touchesBegan method like this:基于 UIImageView 创建一个新类,将其用户交互设置为 YES,在 UIView 中添加两个实例,并实现其 touchesBegan 方法,如下所示:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.tag == 1) {
        self.backgroundColor= [UIColor redColor];
    }

    else {
    dispatch_async(dispatch_get_main_queue(), ^{
    [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
  });
    [UIView animateWithDuration:0.25 animations:
        ^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
    }
}

Assign the tag 1 to one of these imageViews.将标签 1 分配给这些 imageView 之一。

What happens exactly when you tap the two views almost simultaneously, starting with the view that loads an image?当您从加载图像的视图开始几乎同时点击两个视图时,究竟会发生什么? Does the UI get blocked because it's waiting for [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]]; UI 是否因为等待[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]]; to return ?返回 ? If so, how may I do this asynchronously ?如果是这样,我该如何异步执行此操作?

Here is a project on github with ipmcc code这是github上的一个项目,带有ipmcc代码

Use a long press then drag to draw a rectangle around the black squares.使用长按然后拖动以在黑色方块周围绘制一个矩形。 As I understand his answer, in theory the white selection rectangle should not be blocked the first time the image is loaded, but it actually is.据我了解他的回答,理论上白色选择矩形不应该在第一次加载图像时被阻止,但实际上是这样。

Two images are included in the project (one small: woodenTile.jpg and one larger: bois.jpg).项目中包含两张图片(一张小的:woodenTile.jpg,一张大的:bois.jpg)。 The result is the same with both.结果与两者相同。

Image format图片格式

I don't really understand how this is related to the problem I still have with the UI being blocked while the image is loaded for the first time, but PNG images decode without blocking the UI, while JPG images do block the UI.我真的不明白这与我在第一次加载图像时仍然遇到的 UI 被阻塞的问题有什么关系,但是 PNG 图像在不阻塞 UI 的情况下解码,而 JPG 图像确实阻塞了 UI。

Chronology of the events事件年表

步骤 0步骤1

The blocking of the UI begins here.. UI的阻塞从这里开始..

第2步

.. and ends here. .. 到此结束。

第 3 步

AFNetworking solution AF网络解决方案

    NSURL * url =  [ [NSBundle mainBundle]URLForResource:@"bois" withExtension:@"jpg"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    [self.imageView setImageWithURLRequest:request
                          placeholderImage:[UIImage imageNamed:@"placeholder.png"]
                                   success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                                       NSLog(@"success: %@", NSStringFromCGSize([image size]));
                                   } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                       NSLog(@"failure: %@", response);
                                   }];

// this code works. Used to test that url is valid. But it's blocking the UI as expected.
if (false)       
if (url) {
        [self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }

Most of the time, it logs: success: {512, 512}大多数时候,它会记录: success: {512, 512}

It also occasionnaly logs: success: {0, 0}它还偶尔记录: success: {0, 0}

And sometimes: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...有时: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...

But the image is never changed.但图像从未改变。

The problem is that UIImage doesn't actually read and decode the image until the first time it's actually used/drawn. 问题是UIImage实际上并没有读取和解码图像,直到它第一次实际使用/绘制。 To force this work to happen on a background thread, you have to use/draw the image on the background thread before doing the main thread -setImage: . 要在后台线程上强制执行此工作,您必须在执行主线程-setImage:之前在后台线程上使用/绘制图像-setImage: This worked for me: 这对我有用:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    UIImage * img = [UIImage imageNamed:@"background.jpg"];

    // Make a trivial (1x1) graphics context, and draw the image into it
    UIGraphicsBeginImageContext(CGSizeMake(1,1));
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
    UIGraphicsEndImageContext();

    // Now the image will have been loaded and decoded and is ready to rock for the main thread
    dispatch_sync(dispatch_get_main_queue(), ^{
        [[self imageView] setImage: img];
    });
});

EDIT: The UI isn't blocking. 编辑:用户界面没有阻止。 You've specifically set it up to use UILongPressGestureRecognizer which waits, by default, a half a second before doing anything. 您已经专门设置它以使用UILongPressGestureRecognizer ,默认情况下,在执行任何操作之前等待半秒钟。 The main thread is still processing events, but nothing is going to happen until that GR times out. 主线程仍在处理事件,但在GR超时之前不会发生任何事情。 If you do this: 如果你这样做:

    longpress.minimumPressDuration = 0.01;

...you'll notice that it gets a lot snappier. ......你会注意到它变得更加快捷。 The image loading is not the problem here. 这里的图像加载不是问题。

EDIT 2: I've looked at the code, as posted to github, running on an iPad 2, and I simply do not get the hiccup you're describing. 编辑2:我看过代码,发布到github,在iPad 2上运行,我根本没有得到你描述的打嗝。 In fact, it's quite smooth. 事实上,它非常顺利。 Here's a screenshot from running the code in the CoreAnimation instrument: 以下是在CoreAnimation工具中运行代码的屏幕截图:

在此输入图像描述

As you can see on the top graph, the FPS goes right up to ~60FPS and stays there throughout the gesture. 正如您在顶部图表中看到的那样,FPS最高可达~60FPS并在整个手势中保持不变。 On the bottom graph, you can see the blip at about 16s which is where the image is first loaded, but you can see that there's not a drop in the frame rate. 在底部的图表中,您可以看到大约16s的blip,这是图像首次加载的位置,但您可以看到帧速率没有下降。 Just from visual inspection, I see the selection layer intersect, and there's a small, but observable delay between the first intersection and the appearance of the image. 仅从视觉检查,我看到选择层相交,并且在第一个交叉点和图像外观之间存在一个小但可观察到的延迟。 As far as I can tell, the background loading code is doing its job as expected. 据我所知,后台加载代码正在按预期执行。

I wish I could help you more, but I'm just not seeing the problem. 我希望我能帮助你更多,但我只是没有看到问题。

You can use AFNetworking library , in which by importing the category 您可以使用AFNetworking库,通过导入类别

"UIImageView+AFNetworking.m" and by using the method as follows : “UIImageView + AFNetworking.m”并使用如下方法:

[YourImageView setImageWithURL:[NSURL URLWithString:@"http://image_to_download_from_serrver.jpg"] 
      placeholderImage:[UIImage imageNamed:@"static_local_image.png"]
               success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                  //ON success perform 
               }
               failure:NULL];

hope this helps . 希望这可以帮助 。

I had a very similar issue with my application where I had to download lot of images and along with that my UI was continuously updating. 我的应用程序有一个非常类似的问题,我必须下载大量图像,并且我的UI不断更新。 Below is the simple tutorial link which resolved my issue: 以下是解决我的问题的简单教程链接:

NSOperations & NSOperationQueues Tutorial NSOperations和NSOperationQueues教程

this is the good way: 这是好方法:

-(void)updateImageViewContent {
    dispatch_async(dispatch_get_main_queue(), ^{

        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        [[self imageView] setImage:img];
    });
}

Why don't you use third party library like AsyncImageView ? 为什么不使用像AsyncImageView这样的第三方库? Using this, all you have to do is declare your AsyncImageView object and pass the url or image you want to load. 使用它,您所要做的就是声明您的AsyncImageView对象并传递您要加载的url或图像。 An activity indicator will display during the image loading and nothing will block the UI. 在图像加载期间将显示活动指示器,并且不会阻止UI。

-(void)touchesBegan: is called in the main thread. - (void)touchesBegan:在主线程中调用。 By calling dispatch_async(dispatch_get_main_queue) you just put the block in the queue. 通过调用dispatch_async(dispatch_get_main_queue),您只需将块放入队列中。 This block will be processed by GCD when the queue will be ready (ie system is over with processing your touches). 当队列准备好时(即系统结束并处理您的触摸),此块将由GCD处理。 That's why you can't see your woodenTile loaded and assigned to self.image until you release your finger and let GCD process all the blocks that have been queued in the main queue. 这就是为什么你不能看到你的woodenTile被加载并分配给self.image,直到你释放你的手指并让GCD处理所有在主队列中排队的块。

Replacing : 更换:

    dispatch_async(dispatch_get_main_queue(), ^{
    [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
  });

by : 通过:

[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];

should solve your issue… at least for the code that exhibits it. 应该解决你的问题...至少对于展示它的代码。

Consider using SDWebImage : it not only downloads and caches the image in the background, but also loads and renders it. 考虑使用SDWebImage :它不仅可以在后台下载和缓存图像,还可以加载和渲染它。

I've used it with good results in a tableview that had large images that were slow to load even after downloaded. 我已经在tableview中使用了它,结果很好,即使在下载之后也有很慢的加载速度。

https://github.com/nicklockwood/FXImageView https://github.com/nicklockwood/FXImageView

This is an image view which can handle background loading. 这是一个可以处理后台加载的图像视图。

Usage 用法

FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;

//show placeholder
imageView.processedImage = [UIImage imageNamed:@"placeholder.png"];

//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];

To get an URL for your file you might find the following interesting: 要获取文件的URL,您可能会发现以下内容:

Getting bundle file references / paths at app launch 在应用启动时获取捆绑文件引用/路径

When you are using AFNetwork in an application, you do not need to use any block for load image because AFNetwork provides solution for it. 在应用程序中使用AFNetwork时,您无需使用任何块来加载映像,因为AFNetwork为其提供了解决方案。 As below: 如下:

#import "UIImageView+AFNetworking.h"

And

   Use **setImageWithURL** function of AFNetwork....

Thanks 谢谢

One way i've implemented it is the Following: (Although i do not know if it's the best one) 我实现它的一种方法是以下:(虽然我不知道它是否是最好的一个)

At first i create a queue by using Serial Asynchronous or on Parallel Queue 首先,我使用串行异步或并行队列创建队列

queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**

or

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

** **

Which ever you may find better for your needs. 您可能会发现哪种更适合您的需求。

Afterwards: 然后:

 dispatch_async( queue, ^{



            // Load UImage from URL
            // by using ImageWithContentsOfUrl or 

            UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

            // Then to set the image it must be done on the main thread
            dispatch_sync( dispatch_get_main_queue(), ^{
                [page_cover setImage: imagename];
                imagename = nil;
            });

        });

There is a set of methods introduced to UIImage in iOS 15 to decode images and create thumbnails asynchronously on background thread iOS 15 中的UIImage引入了一组方法,用于在后台线程上异步解码图像和创建缩略图

func prepareForDisplay(completionHandler: (UIImage?) -> Void) func prepareForDisplay(completionHandler: (UIImage?) -> Void)

Decodes an image asynchronously and provides a new one for display in views and animations.异步解码图像并提供新图像以在视图和动画中显示。

func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void) func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void)

Creates a thumbnail image at the specified size asynchronously on a background thread.在后台线程上异步创建指定大小的缩略图。

You can also use a set of similar synchronous APIs, if you need more control over where you want the decoding to happen, eg specific queue:如果您需要更多地控制您希望解码发生的位置,例如特定队列,您还可以使用一组类似的同步API:
func preparingForDisplay() -> UIImage? func prepareForDisplay() -> UIImage?
func preparingThumbnail(of: CGSize) -> UIImage? func 准备Thumbnail(of: CGSize) -> UIImage?

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

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