我有一个带有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];
});
});
}
图像很小(150x100)。
但是,加载图像时仍然会阻止UI。我错过了什么?
以下是一个展示此行为的小代码示例:
基于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)];}];
}
}
将标记1分配给其中一个imageView。
从加载图像的视图开始,几乎同时点击两个视图时会发生什么? UI是否因为等待[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
返回而被阻止?如果是这样,我该如何异步执行此操作?
这是一个带有ipmcc代码的project on github
使用长按然后拖动在黑色方块周围绘制一个矩形。据我理解他的答案,理论上白色选择矩形不应该在第一次加载图像时被阻挡,但实际上是。
项目中包含两个图像(一个小图片:woodenTile.jpg和一个较大图片:bois.jpg)。结果与两者相同。
我真的不明白这与第一次加载图像时UI被阻止的问题有什么关系,但是PNG图像在不阻挡UI的情况下进行解码,而JPG图像会阻止UI
阻止UI从这里开始..
..并在此结束。
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]]]; }
大部分时间,它会记录:success: {512, 512}
它还偶尔记录:success: {0, 0}
有时:failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...
但图像永远不会改变。
答案 0 :(得分:53)
问题是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];
});
});
编辑:用户界面没有阻止。您已经专门设置它使用UILongPressGestureRecognizer
,默认情况下等待半秒钟才能执行任何操作。主线程仍在处理事件,但在GR超时之前不会发生任何事情。如果你这样做:
longpress.minimumPressDuration = 0.01;
...你会发现它变得更加快捷。图像加载不是问题所在。
编辑2:我看过代码,发布到github,在iPad 2上运行,我根本没有得到你描述的打嗝。事实上,它非常顺利。以下是在CoreAnimation工具中运行代码的屏幕截图:
正如您在顶部图表中看到的那样,FPS最高可达~60FPS并在整个手势中保持不变。在底部的图表中,您可以看到大约16s的blip,这是图像首次加载的位置,但您可以看到帧速率没有下降。仅从视觉检查,我看到选择层相交,并且在第一个交叉点和图像外观之间存在一个小但可观察到的延迟。据我所知,后台加载代码正在按预期工作。
我希望我能帮助你更多,但我只是没有看到问题。
答案 1 :(得分:5)
您可以使用AFNetworking 库,其中导入类别
“的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];
希望这会有所帮助。
答案 2 :(得分:4)
我的应用程序遇到了一个非常类似的问题,我不得不下载大量图片,并且我的UI不断更新。以下是解决我的问题的简单教程链接:
答案 3 :(得分:3)
这是好方法:
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
[[self imageView] setImage:img];
});
}
答案 4 :(得分:2)
为什么不使用像AsyncImageView这样的第三方库?使用它,您所要做的就是声明您的AsyncImageView对象并传递您要加载的url或图像。在图像加载期间将显示活动指示器,并且不会阻止UI。
答案 5 :(得分:1)
- (void)touchesBegan:在主线程中调用。通过调用dispatch_async(dispatch_get_main_queue),您只需将块放入队列中。当队列准备就绪时,该块将由GCD处理(即系统结束并处理您的触摸)。这就是为什么你不能看到你的woodenTile加载并分配给self.image,直到你释放你的手指并让GCD处理所有在主队列中排队的块。
更换:
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
});
by:
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
应解决您的问题...至少对于展示它的代码。
答案 6 :(得分:0)
考虑使用SDWebImage:它不仅可以在后台下载和缓存图像,还可以加载和渲染图像。
我在桌面视图中使用了它,结果很好,即使在下载后也会出现加载速度很慢的大图像。
答案 7 :(得分:0)
https://github.com/nicklockwood/FXImageView
这是一个可以处理后台加载的图像视图。
用法
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];
要获取文件的网址,您可能会发现以下内容:
答案 8 :(得分:0)
在应用程序中使用AFNetwork时,您无需使用任何块来加载图像,因为AFNetwork为其提供了解决方案。如下:
#import "UIImageView+AFNetworking.h"
并且
Use **setImageWithURL** function of AFNetwork....
由于
答案 9 :(得分:0)
我实现它的一种方法是以下:(虽然我不知道它是否是最好的一种)
首先,我使用串行异步或并行队列
创建队列queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**
or
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
**
您可能会更好地满足您的需求。
然后:
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;
});
});