UIImage 从服务器逐步显示
Posted
技术标签:
【中文标题】UIImage 从服务器逐步显示【英文标题】:UIImage to be displayed progressively from server 【发布时间】:2012-02-28 07:56:13 【问题描述】:我一直在尝试显示来自服务器的大图像,但我必须逐步显示它。
我使用了 UIView 的子类,其中我使用了UIImage
对象,其中我使用了 NSURLConnection 及其委托方法,我也使用了
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
我在其中附加数据并将其转换为UIImage
对象,并使用UIImage
的drawInRect:
方法绘制矩形。
一切正常,但问题是,当在上下文中绘制图像时,在将整个图像绘制到屏幕上之前,我无法单击屏幕上的其他任何位置。
有什么好的解决方案,即使在屏幕上绘制图像,我也可以点击其他任何地方?
任何帮助都将不胜感激。
编辑:
在didReceiveData
中是否有任何有效的方法可以逐渐模糊图像?所以drawInRect
不会花太多时间来画画。或者如果有人有自定义的drawRect
方法,它可以有效地将图像逐步显示为didReceiveData
中接收到的数据。
【问题讨论】:
如何在后台线程中运行进程? 我尝试在后台运行进程,但它崩溃了,因为我们无法在后台线程中在屏幕上绘制图像,它应该在主线程中。 这里的问题是你不断地重绘图像,这阻止了 UI 负责。您可以做的是“过滤”重绘,每 10 或 50 次 didReceiveData 迭代进行一次。 是的,你说得对,在 didReceiveData 中多次绘制图像,会产生问题。有没有其他方法可以逐步绘制图像? 如果您的图像是 jpeg 格式,您是否尝试过使用渐进式 jpeg (faqs.org/faqs/jpeg-faq/part1/section-11.html)?我不知道这是否会有所帮助,但你可以试试。 【参考方案1】:我曾将 NYXImagesKit 用于类似的事情,在不阻塞主线程的情况下下载图像并逐步显示图像。我写了一个非常快速和肮脏的例子来说明基本的工作原理。我在 UITableview 中加载图像以显示它不会阻塞用户界面(主线程)。您可以在图像加载时滚动表格视图。不要忘记添加正确的框架,有几个。这是 Github 上项目的链接:
https://github.com/HubertK/ProgressiveImageDownload
它真的很容易使用,创建一个 NYXProgressiveImageView 对象,设置 URL,它会在您调用时为您完成所有工作:
loadImageAtURL:
它是 UIImageView 的子类,像魔术一样工作!这是开发者网站的链接:
http://www.cocoaintheshell.com/2012/01/nyximageskit-class-nyxprogressiveimageview/
【讨论】:
【参考方案2】:我建议以异步方式拉取图像数据,然后应用校正以获得从部分下载的 NSData 到 UIImage 的有效转换:
NSURLRequest *theRequest = [NSURLRequest requestWithURL:
[NSURL URLWithString: imageRequestString]
cachePolicy: NSURLRequestReloadIgnoringCacheData
timeoutInterval: 60.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest: theRequest
delegate: self];
if (theConnection)
receivedData = [[NSMutableData data] retain];
.......
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
[receivedData appendData: data];
NSInvocationOperation *operation =
[[NSInvocationOperation alloc] initWithTarget: self
selector: @selector(loadPartialImage)
object: nil];
[[[NSOperationQueue alloc] init] autorelease] addOperation: operation];
[operation release];
- (void)loadPartialImage
// This is where you would call the function that would "stitch up" your partial
// data and make it appropriate for use in UIImage's imageWithData
NSData *validPartialData =
[self validImageRepresentationFromPartialImageData: receivedData];
UIImage *partialImage = [UIImage imageWithData: validPartialData];
[imageView performSelectorOnMainThread: @selector(setImage:)
withObject: partialImage
waitUntilDone: NO];
+ (void)connectionDidFinishLoading:(NSURLConnection *)connection
[connection release];
UIImage *fullImage = [UIImage imageWithData: receivedData];
imageView.image = fullImage;
请注意,我没有提供validImageRepresentationFromPartialImageData
的代码,因为目前我没有明确的具体想法,关于如何实现这样的更正,或者 [UIImage imageWithData:] 实际上不会默认接受部分数据作为输入。如您所见,强制转换和 UIImage 创建将发生在不同的线程上,而主线程只会在更新时显示更新。
如果您收到过于频繁的更新并且它们仍然阻止界面,您可以:
一个。也在不同的线程上发出图像请求。 湾。减少 UIImageView 的更新频率,根据图像的大小,在 10 或 100 次更新中只调用一次 setImage。
【讨论】:
感谢您的回复。是的,我在做“a”。你提到的选项,但我担心有没有更好的方法在屏幕上绘制图像而不阻塞界面。可能是一种更好的方式来绘制图像,这样它就可以花费很少的时间来绘制。【参考方案3】:我通常使用非常简单的 GCD 模式进行异步图像加载:
-
创建一个 GCD 队列,您可以在其中从 Web 服务器加载图像数据
在主队列中设置图像数据
示例:
dispatch_queue_t image_queue = dispatch_queue_create("com.company.app.imageQueue", NULL);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(image_queue, ^
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[record imageURLString]];
dispatch_async(main_queue, ^
[imageView setImage:[UIImage imageWithData:imageData]];
);
);
【讨论】:
感谢您的回答,但是在 didReceiveData: 方法中绘制图像时出现问题,我无法单击屏幕上的其他任何位置。并且绘图图像应该在主线程中,因为您的回答表明主队列也将在主线程中,所以它会挂起直到图像没有被绘制,因为 didReceiveData: 会被调用很多次...... @JigneshBrahmkhatri 不能先收集所有图像数据,然后将其绘制到屏幕上吗?我想这样更有效率 @JigneshBrahmkhatri Mhh,好的。也许您可以显示一个微调器,它表示加载进度?我认为这是一种比阻塞 UI 线程更好的方法。 我的第一个要求是从服务器逐步显示图像。 你能给我更多的代码吗?我没有时间从头开始写下你的方法——如果我有你实际使用的控制器会更容易帮助你【参考方案4】:可能didReceiveData
被调用太频繁了!只需使用NSTimer
并以 1-2 秒的步骤定期更新图像。这应该更有效。
您还可以使用performSelectorInBackground
将您的NSData
转换为UIImage
;
然后调用performSelectorOnMainThread
将图像设置到 UIImage 视图中。所以转换的东西不会阻塞主线程。
【讨论】:
感谢您的回复,正如您所说,我正在使用替代解决方案,但我需要适当的解决方案来完成它,因为,我将调用 40-50 UIImageViews 从服务器加载并逐步显示它。所以这个解决方案也行不通。问题是,在屏幕上绘制图像需要太多时间。 绘图时间过长!?你测量了吗?就像我说的那样减少更新计数。那应该工作,不是吗?最终加载多少视图并不重要,只要您正确地选择其中一个即可。只是不要重绘来自 didReceiveData 的每次更新。 我每 20 次更新重绘,控制来自 didReceiveData,但这是针对一个图像,如果有 100 个图像,那么控制可以随时到来,图像将在主线程中绘制,这会挂起应用程序直到正在完全绘制图像。 好的,我们需要更多详细信息。你在说什么图像尺寸?一次将加载多少个?为什么需要渐进式显示? 我需要渐进式显示以确保用户正在加载图像,这是我不能离开这个要求的第一个要求。关于图像,它们来自服务器,可以是 10 或 50【参考方案5】:您是否考虑过在服务器上将图像分割成更小的块,然后在收到完整的块时重新绘制?这将使您可以通过更改块大小来控制负载的“渐进性”和重绘频率。不过,不确定这是您所追求的渐进式负载。
【讨论】:
【参考方案6】:如果您可以控制服务器,请将图像分割成图块并创建低分辨率图像。首先在最低层显示低分辨率版本,然后在加载时将图块加载到顶部绘制它们?
【讨论】:
【参考方案7】:您可以使用图像的 URL 和 startDownload 方法创建 UIImageView 的子类。 这是一个基本示例,必须改进。
@property (nonatomic, strong) NSURL *imageURL;
- (void)startDownload;
@implementation ImgeViewSubClass
NSURLConnection *connection;
NSMutableData *imageData;
开始下载方法:
- (void)startDownload
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
imageData = [NSMutableData data];
来自NSURLConnectionDataDelegate
的委托方法
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
@synchronized(imageData)
[imageData appendData:data];
// this part must be improved using CGImage instead of UIImage because we are not on main thread
UIImage *image = [UIImage imageWithData:imageData];
if (image)
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
);
【讨论】:
【参考方案8】:答案在 ImageIO.framework 中,其实很简单
首先创建一个 CGImageSourceRef mySource ,使用 CGImageSourceCreateIncremental() 实例化它。
使用图像 URL 设置并启动 NSURLConnection。
在 connection:didReceiveData: 中,将接收到的数据附加到占位符数据中,并通过调用更新图像源
CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, NO);
然后将图像的部分加载部分加载到您的 UIImageView
self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
在connectionDidFinishLoading中:通过调用完成
CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, YES);
self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
CFRelease(imageSource);
imageData = nil;
这是我编写的示例代码:
https://github.com/mohammedDehairy/MDIncrementalImageView
【讨论】:
【参考方案9】:为什么不使用 ASIHTTPRequest 请求:
#import "ASIHTTPRequest.h"
这将有助于在后台加载/绘制,也可以执行其他任务。
试试这个:
#import "ASIHTTPRequest.h"
[self performSelectorInBackground:@selector(DownLoadImageInBackground:)
withObject:YOUR IMAGE ARRAY];
-(void) DownLoadImageInBackground:(NSArray *)imgUrlArr1
NSURL * url = [Image URL];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
-(void)requestFailed:(ASIHTTPRequest *)request
NSLog(@"URL Fail : %@",request.url);
NSError *error = [request error];
// you can give here alert too..
-(void)requestFinished:(ASIHTTPRequest *)request
/////////// Drawing Code Here////////////////////
NSData *responseData = [request responseData];
UIImage *imgInBackground = [[UIImage alloc] initWithData:responseData];
[imageView setImage: imgInBackground];
【讨论】:
感谢您的回复,但我希望图像逐步显示。只有在我收到所有数据后,您的代码才会显示图像。 我没有太多经验...我认为您可以使用网络视图而不是图像视图。有它的委托方法。将在此工作以显示 UIImage 从服务器逐步显示...【参考方案10】:我不确定您的代码的其他部分(注册此模块)是如何实现的,但请尝试以下操作,
尝试使用此选择器并将运行循环模式设置为NSDefaultRunLoopMode
[self performSelectorOnMainThread:@selector(processImage:)
withObject:objParameters
waitUntillDone:NO
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]
此执行将释放您的 UI 交互,如果有帮助请告诉我。
欲了解更多信息:APPLE DOCS
【讨论】:
正如您在上述线程中提到的,您需要异步下载大约 40-50 张图像,这应该会有所帮助。让我知道它,成功时可能是最佳实践。【参考方案11】://JImage.h
#import <Foundation/Foundation.h>
@interface JImage : UIImageView
NSURLConnection *connection;
NSMutableData* data;
UIActivityIndicatorView *ai;
-(void)initWithImageAtURL:(NSURL*)url;
@property (nonatomic, retain) NSURLConnection *connection;
@property (nonatomic, retain) NSMutableData* data;
@property (nonatomic, retain) UIActivityIndicatorView *ai;
@end
//JImage.m
#import "JImage.h"
@implementation JImage
@synthesize ai,connection, data;
-(void)initWithImageAtURL:(NSURL*)url
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self setContentMode:UIViewContentModeScaleToFill];
if (!ai)
[self setAi:[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]];
[ai startAnimating];
[ai setFrame:CGRectMake(27.5, 27.5, 20, 20)];
[ai setColor:[UIColor blackColor]];
[self addSubview:ai];
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData
if (data==nil)
data = [[NSMutableData alloc] initWithCapacity:5000];
[data appendData:incrementalData];
NSNumber *resourceLength = [NSNumber numberWithUnsignedInteger:[data length]];
NSLog(@"resourceData length: %d", [resourceLength intValue]);
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
NSLog(@"Connection error...");
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[ai removeFromSuperview];
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self setImage:[UIImage imageWithData: data]];
[ai removeFromSuperview];
@end
//Include the definition in your class where you want to use the image
-(UIImageView*)downloadImage:(NSURL*)url:(CGRect)frame
JImage *photoImage=[[JImage alloc] init];
photoImage.backgroundColor = [UIColor clearColor];
[photoImage setFrame:frame];
[photoImage setContentMode:UIViewContentModeScaleToFill];
[photoImage initWithImageAtURL:url];
return photoImage;
//call the function
UIImageView *imagV=[self downloadImage:url :rect];
//you can call the downloadImage function in looping statement and subview the returned imageview.
//it will help you in lazy loading of images.
//Hope this will help
【讨论】:
嗨,Kuldeep,感谢分享答案,但这不是我想要的方式。我想在 didReceiveData 方法中检索图像时逐步显示图像。所以我想在 didReceiveData 中设置图像,它会挂起应用程序直到设置图像。我想要相同的功能而不需要挂起交互。以上是关于UIImage 从服务器逐步显示的主要内容,如果未能解决你的问题,请参考以下文章
UIImageView 不显示从通知中获取的 UIImage
UIImage 的 -imageNamed: 方法是不是适用于存储在“文档”中的图像文件?