UIImage to be displayed progressively from server

2019-01-22 13:36发布

问题:

I have been trying to display large image from server, but I have to display it progressively.

I used subclass of UIView and in that I have taken UIImage object, in which I used NSURLConnection and its delegate methods, I also used

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

in which I am appending data and converting it to UIImage object, and drawing rect using the drawInRect: method of UIImage.

Everything is working fine, but the problem is, when image is being drawn on context, I cannot click anywhere else on screen until entire image is being drawn on to screen.

Is there any good solution, where I can click anywhere else even if image is being drawn on screen?

Any help will be appreciable.

Edit: Is there any efficient way of drawing image blurry progressively in didReceiveData? so drawInRect does not take too much time to draw. Or If anyone has custom drawRect method which efficiently displays image progressively as data received in didReceiveData.

回答1:

I have used NYXImagesKit for something similar, downloading images while not blocking the main thread and showing the image progressively. Ive written a really quick and dirty example to illustrate the basic workings. I load the image in a UITableview to show that it doesn't block the User Interface(Main Thread). You can scroll the tableview while the image is loading. Don't forget to add the correct Frameworks, there are a few. Heres the link to the project on Github:

https://github.com/HubertK/ProgressiveImageDownload

It's really easy to use,create a NYXProgressiveImageView object, set the URL and it will do all the work for you when you call:

loadImageAtURL:

It's a subclass of UIImageView, Works like magic! Here's a link to the developers site:

http://www.cocoaintheshell.com/2012/01/nyximageskit-class-nyxprogressiveimageview/



回答2:

I suggest pulling the image data in an asynchronous manner and then applying a correction in order to obtain a valid conversion from partially downloaded NSData to an 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;
}

Note that I did not provide the code for validImageRepresentationFromPartialImageData, as, at the moment, I have no clear, specific idea, on how to implement such a correction, or if the [UIImage imageWithData:] wouldn't actually accept partial data as input by default. As you can see, the coercion and UIImage creation would happen on a different thread, while the main thread would only display the updates as they come.

If you are receiving too frequent updates and they are still blocking the interface, you could:

a. Make the image requests on a different thread as well. b. Reduce the frequency of the UIImageView's updates, by only calling setImage once in 10 or 100 updates, according to the zise of your image.



回答3:

I usually use a really simple GCD pattern for async image loading:

  1. Create a GCD queue in which you load the image data form your web server
  2. Set the image data in your main queue

Example:

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]];
  });
});


回答4:

Probably didReceiveData is called too often! Just use a NSTimerand update the image regularly in 1-2second steps. That should work more efficiently.

Also you can use performSelectorInBackgroundto convert your NSData to an UIImage; And then call performSelectorOnMainThreadto set the image into the UIImage View. So the converting stuff won't block the main thread.



回答5:

Have you considered chopping up your images into smaller chunks on the server, then redrawing whenever a complete chunk has been received? This would give you control over the "progressiveness" of the load and the frequency of redraws by changing the chunk size. Not sure this is the kind of progressive load you're after, though.



回答6:

If you have control of the server, split the image into tiles and also create a low res image. Display the low res version first in the lowest layer, and load the tiles on top drawing them as they load?



回答7:

You can create a subclass of UIImageView with the URL of the image and a startDownload method. It's a basic sample it must be improved.

@property (nonatomic, strong) NSURL *imageURL;
- (void)startDownload;

@implementation ImgeViewSubClass
{
    NSURLConnection *connection; 
    NSMutableData *imageData;

}

The start download method:

- (void)startDownload
{ 
    NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
    connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [connection start];
    imageData = [NSMutableData data];

}

Delegate method from 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:

The Answer is in ImageIO.framework , its very simple actually

  1. first you create a CGImageSourceRef mySource ,instantiate it using CGImageSourceCreateIncremental() .

  2. setup and start an NSURLConnection with the image Url.

  3. in connection:didReceiveData: , append the received data to your placeholder data , and update the image source by calling

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

then load the partially loaded part of the image to your UIImageView

self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
  1. in connectionDidFinishLoading: finalise by calling

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

    self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];

    CFRelease(imageSource);

    imageData = nil;

here is a sample code i wrote :

https://github.com/mohammedDehairy/MDIncrementalImageView



回答9:

Why don't you use ASIHTTPRequest request:

#import "ASIHTTPRequest.h"

This will help to load/draw in background, can perform other task too.

Try this one:

#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];
}


回答10:

I am not sure how the other parts of your code(reg this module) is implemented but give the following a try,

Try to use this selector with the run loop mode set to NSDefaultRunLoopMode

[self performSelectorOnMainThread:@selector(processImage:)
                   withObject:objParameters
               waitUntillDone:NO
                        modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]

This execution will free up your UI interactions, let me know if it helped please.

For more info : APPLE DOCS



回答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