ios 如何在 UIImageView 中异步加载图像?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19179185/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to asynchronously load an image in an UIImageView?
提问by alecail
I have an UIView with an UIImageView subview. I need to load an image in the UIImageView without blocking the UI. The blocking call seems to be: UIImage imageNamed:
. Here is what I thought solved this problem:
我有一个带有 UIImageView 子视图的 UIView。我需要在 UIImageView 中加载图像而不阻塞 UI。阻塞调用似乎是:UIImage imageNamed:
。这是我认为解决这个问题的方法:
-(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. What am I missing ?
但是,加载图像时 UI 仍然被阻止。我错过了什么?
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 分配给这些 imageViews 之一。
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"]];
to return ? If so, how may I do this asynchronously ?
从加载图像的视图开始,几乎同时点击两个视图会发生什么?UI 是否因为等待[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
返回而被阻止?如果是这样,我如何异步执行此操作?
Here is a project on githubwith ipmcc code
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). The result is the same with both.
项目中包含两张图片(一张小图片:woodTile.jpg,一张大图片:bois.jpg)。两者的结果是一样的。
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
事件的年表
The blocking of the UI begins here..
UI 的阻塞从这里开始..
.. and ends here.
.. 到此结束。
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.
但形象永远不会改变。
回答by ipmcc
The problem is that UIImage
doesn't actually read and decode the image until the first time it's actually used/drawn. 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:
. This worked for me:
问题是UIImage
直到第一次实际使用/绘制图像时,它才真正读取和解码图像。要强制此工作在后台线程上发生,您必须在执行主线程之前在后台线程上使用/绘制图像-setImage:
。这对我有用:
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. The main thread is still processing events, but nothing is going to happen until that GR times out. If you do this:
编辑:用户界面没有阻塞。您已经专门将其设置为使用UILongPressGestureRecognizer
which 在执行任何操作之前默认等待半秒。主线程仍在处理事件,但在 GR 超时之前不会发生任何事情。如果你这样做:
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. In fact, it's quite smooth. Here's a screenshot from running the code in the CoreAnimation instrument:
编辑 2:我查看了在 iPad 2 上运行的发布到 github 的代码,但我根本没有遇到您所描述的问题。事实上,它非常流畅。这是在 CoreAnimation 工具中运行代码的屏幕截图:
As you can see on the top graph, the FPS goes right up to ~60FPS and stays there throughout the gesture. 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. 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.
如上图所示,FPS 最高可达约 60FPS,并在整个手势过程中保持不变。在底部的图表中,您可以看到大约 16 秒处的 blip,这是图像首次加载的位置,但您可以看到帧速率没有下降。仅从目视检查中,我看到选择图层相交,并且在第一个相交和图像外观之间有一个很小但可以观察到的延迟。据我所知,后台加载代码正在按预期工作。
I wish I could help you more, but I'm just not seeing the problem.
我希望我能帮助你更多,但我只是没有看到问题。
回答by itechnician
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 .
希望这可以帮助 。
回答by Geekoder
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. Below is the simple tutorial link which resolved my issue:
我的应用程序有一个非常相似的问题,我必须下载大量图像,而且我的 UI 不断更新。以下是解决我的问题的简单教程链接:
回答by Mirko Catalano
this is the good way:
这是好方法:
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
[[self imageView] setImage:img];
});
}
回答by Luciano Rodríguez
Why don't you use third party library like AsyncImageView? Using this, all you have to do is declare your AsyncImageView object and pass the url or image you want to load. An activity indicator will display during the image loading and nothing will block the UI.
为什么不使用像AsyncImageView这样的第三方库?使用它,您所要做的就是声明您的 AsyncImageView 对象并传递您要加载的 url 或图像。加载图像期间将显示活动指示器,并且不会阻止 UI。
回答by Patrock
-(void)touchesBegan: is called in the main thread. By calling dispatch_async(dispatch_get_main_queue) you just put the block in the queue. This block will be processed by GCD when the queue will be ready (i.e. system is over with processing your touches). 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.
-(void)touchesBegan:在主线程中调用。通过调用 dispatch_async(dispatch_get_main_queue) 您只需将块放入队列中。当队列准备好(即系统处理您的触摸结束)时,GCD 将处理此块。这就是为什么在您松开手指并让 GCD 处理所有已在主队列中排队的块之前,您无法看到您的 woodTile 加载并分配给 self.image 的原因。
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.
应该可以解决您的问题……至少对于展示它的代码。
回答by Lescai Ionel
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 中使用它取得了良好的效果,该表格视图具有即使下载后加载速度也很慢的大图像。
回答by Marc
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,您可能会发现以下有趣的内容:
回答by Hindu
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. As below:
当您在应用程序中使用 AFNetwork 时,您不需要使用任何块来加载图像,因为 AFNetwork 为其提供了解决方案。如下:
#import "UIImageView+AFNetworking.h"
And
和
Use **setImageWithURL** function of AFNetwork....
Thanks
谢谢
回答by hyphestos
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;
});
});