【问题标题】:UIImage to be displayed progressively from serverUIImage 从服务器逐步显示
【发布时间】:2012-02-28 07:56:13
【问题描述】:

我一直在尝试显示来自服务器的大图像,但我必须逐步显示它。

我使用了 UIView 的子类,其中我使用了UIImage 对象,其中我使用了 NSURLConnection 及其委托方法,我也使用了

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

我在其中添加数据并将其转换为UIImage 对象,并使用UIImagedrawInRect: 方法绘制矩形。

一切正常,但问题是,当在上下文中绘制图像时,在将整个图像绘制到屏幕上之前,我无法单击屏幕上的其他任何位置。

有什么好的解决方案,即使在屏幕上绘制图像,我也可以点击其他任何地方?

任何帮助都将不胜感激。

编辑: 在didReceiveData 中是否有任何有效的方法可以逐渐模糊图像?所以drawInRect 不会花太多时间来画画。或者如果有人有自定义的drawRect 方法,该方法可以有效地将图像逐步显示为didReceiveData 中接收到的数据。

【问题讨论】:

  • 如何在后台线程中运行进程?
  • 我尝试在后台运行进程,但它崩溃了,因为我们无法在后台线程中在屏幕上绘制图像,它应该在主线程中。
  • 这里的问题是你不断地重绘图像,这阻止了 UI 负责。您可以做的是“过滤”重绘,每 10 或 50 次 didReceiveData 迭代进行一次。
  • 是的,你说得对,在 didReceiveData 中多次绘制图像,会产生问题。有没有其他方法可以逐步绘制图像?
  • 如果您的图片是 jpeg 格式,您是否尝试过使用渐进式 jpeg (faqs.org/faqs/jpeg-faq/part1/section-11.html)?我不知道这是否会有所帮助,但你可以试试。

标签: iphone uiimageview uiimage http-headers nsurlconnection


【解决方案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 模式进行异步图像加载:

    1. 创建一个 GCD 队列,您可以在其中从 Web 服务器加载图像数据
    2. 在主队列中设置图像数据

    示例:

    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 中,其实很简单

          1. 首先创建一个 CGImageSourceRef mySource ,使用 CGImageSourceCreateIncremental() 实例化它。

          2. 使用图像 URL 设置并启动 NSURLConnection。

          3. 在 connection:didReceiveData: 中,将接收到的数据附加到占位符数据中,并通过调用更新图像源

          CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, NO);

          然后将图像的部分加载部分加载到您的 UIImageView

          self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
          
          1. 在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 中设置图像,它会挂起应用程序直到设置图像。我想要相同的功能而不需要挂起交互。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-04-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多