Loading an image from a URL but displaying it progressively - objective-c

I have a screen that will load around 5 images, but they are huge images. Right now I use a
NSURLRequest
and a:
connectionDidFinishLoading
..for callback to tell me when each image is loaded.
The problem is that images would pop up one by one. Is there a way to have it display the image while it loads?
Thanks

The guts of what you need to do this are available as CGImageSource methods.
First, you use an asynchronous NSURLConnection to get the data. You add received data to a NSMutableData object as it arrives, so the data object gets bigger and bigger til finished.
You also create a progressive image source:
CGImageSourceRef imageSourcRef = CGImageSourceCreateIncremental(dict);
You will find lots of examples here and on google how to set the dictionary required.
Then as the data arrives, you pass the TOTAL data object into this method:
CGImageSourceUpdateData(imageSourcRef, (__bridge CFDataRef)data, NO); // No means not finished
You can then ask the image source for an image, which will be partial as the image is downloading. With a CGImage you can create a UIImage.
When you get the final data, you update the image source on last time:
CGImageSourceUpdateData(imageSourcRef, (__bridge CFDataRef)data, YES);
You then use the image source to get a final image and you're done.

Displaying it while loading ,I don't think UIImageView can load UIImageswith incomplete data while loading.I will go for
AsyncImageView ,
It can deal with all the burden of loading image asynchronous.Also UIActivityIndicator is already added to it.So it will be more user friendly

Use blocks and GCD's dispatch_async method.
Look at this example:
//communityDetailViewController.h
#interface communityDetailViewController : UIViewController {
UIImageView *imgDisplay;
UIActivityIndicatorView *activity;
// the dispatch queue to load images
dispatch_queue_t queue;
}
#end
//communityDetailViewController.m
- (void)loadImage
{
[activity startAnimating];
NSString *url = #"URL the image";
if (!queue) {
queue = dispatch_queue_create("image_queue", NULL);
}
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
UIImage *anImage = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[activity stopAnimating];
activity.hidden = YES;
if (anImage != nil) {
[imgDisplay setImage:anImage];
}else{
[imgDisplay setImage:[UIImage imageNamed:#"no_image_available.png"]];
}
});
});
}

You can subclass UIImageView and use this.
-(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
imageData = [NSMutableData data];
imageSize = [response expectedContentLength];
imageSource = CGImageSourceCreateIncremental(NULL);
}
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
[imageData appendData:data];
CGImageSourceUpdateData(imageSource, (__bridge CFDataRef)imageData, ([imageData length] == imageSize) ? true : false);
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (cgImage){
UIImage* img = [[UIImage alloc] initWithCGImage:cgImage scale:1.0f orientation:UIImageOrientationUp];
dispatch_async( dispatch_get_main_queue(), ^{
self.image = img;
});
CGImageRelease(cgImage);
}
}

Related

How to implement lazy loading of image without using SDImageCache?

How to implement lazy loading of image without using below link:
https://github.com/rs/SDWebImage
Note: I don't want to use any third party tool. I want to do from my side.
If you don't want to use any library (some are MIT or public domain licensed, so I think the only reason not to use them is that you want to learn how to build it yourself).
Here is how to do it with a simple and effective way:
1 : Put a temporary placeholder image in your imageView.
2 : Get your image in background thread.
2-a : If you want the cache feature, search for a cached image.
2-b : If no cache feature or no cached image, get your image from its source.
2-c : If cache feature, save the image to cache.
3 : In main thread show your image in the imageView.
Pseudo Code : (I wrote it on the go, it is not meant to run and it may have errors, sorry for that).
-(void) lazilyLoadImageFromURL :(NSURL *)url{
imageView.image = [UIImage imageNamed:#"Placeholder.png];
if([self cachedImageAvailableForURL:url){
imageView.image= [self cachedImageForURL:url];
}
else{
NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse * resp, NSData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),^
{
if ( error == nil && data )
{
UIImage *urlImage = [[UIImage alloc] initWithData:data];
imageView.image = urlImage;
[self saveImageInCache:image forURL:url];
}
});
}];
}
}
-(BOOL) cachedImageAvailableForURL:(NSURL*):url{
// check if there is a saved cached image for this url
}
-(UIImage *) cachedImageForURL:(NSURL*):url{
// returns the cached image for that url
}
-(void) saveImageInCache:(UIImage*) image forURL:(NSURL*)url{
// saves the image in cache for the url
}
Of course, this is only ONE POSSIBLE WAY to do it. I tried to make it simple, but there are plenty better and more complicated ways to do it.
Try it:
NSURL *imageURL = [NSURL URLWithString:#"www...."];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
self.imgVWprofile.image=[UIImage imageWithData:imageData];
});
});

How to retrieve images from server asynchronously

i have one NSMutableArray with some image url's. The images have sizes between 12KB to 6MB. I use AsycImageView class and implement but when large images are downloading application get crashed, I gave 6*1024*1024 (6MB) for maxsize in that class, increase time interval 60.0 sec to 180.o sec, but there is no use. I'm getting an error "Received memory warning" and when app crash automatically connection remove from device, but in simulator there is no crash.
Use GCD for lazy loading.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSString *strURL = url here;
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
UIImage *image = nil;
if(data)
image = [UIImage imageWithData:data];
dispatch_sync(dispatch_get_main_queue(), ^{
//now use image in image View or anywhere according to your requirement.
if(image)
yourImgView = image
});
});
you can do this using multiThreading. Here is a code
- (UIImageView *)getImageFromURL:(NSDictionary *)dict
{
#ifdef DEBUG
NSLog(#"dict:%#", dict);
#endif
UIImageView *_cellImage = nil;
_cellImage = ((UIImageView *)[dict objectForKey:#"image"]);
NSString *strURL = [dict objectForKey:#"imageurl"]);
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
#ifdef DEBUG
NSLog(#"%i", data.length);
#endif
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dataFilePath = [NSString stringWithFormat:#"%#.png", [documentsDirectory stringByAppendingPathComponent:[dict objectForKey:#"imageid"]]];
if (data) // i.e. file exist on the server
{
[data writeToFile:dataFilePath atomically:YES];
_cellImage.image = [UIImage imageWithContentsOfFile:dataFilePath];
}
else // otherwise show a default image.
{
_cellImage.image = [UIImage imageNamed:#"nouser.jpg"];
}
return _cellImage;
}
And call this method in cellForRowAtIndexPath like this:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:imageURL, #"imageurl", self.imgPhoto, #"image", imageid, #"imageid", nil];
[NSThread detachNewThreadSelector:#selector(getImageFromURL:) toTarget:self withObject:dict];
The code will start getting images in multiple threads and will save image locally to document folder. Also the image will not download again if already exists with the same name. Hope this helps
You could download image asynchronously using GCD. Use the following code,
__block NSData *imageData;
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, NULL);
dispatch_async(myQueue, ^{
//load url image into NSData
imageData = [NSData dataWithContentsOfURL: your_image_URL];
if(imageData) {
dispatch_sync(dispatch_get_main_queue(), ^{
//convert data into image after completion
UIImage *img = [UIImage imageWithData:imageData];
//do what you want to do with your image
});
} else {
NSLog(#"image not found at %#", your_image_URL);
}
});
dispatch_release(myQueue);
For further info, see dispatch_queue_t
I would recommend a drop in replacement API SDWebImage it provides a category for UIImageVIew with support for remote images coming from the web. you can also have a placeholder image till the images are downloaded Asynchronously
. Its easy to use and saves a lot of work
You're problem seems to be more of a memory usage issue than it is a performance issue.
If you really want to download image asynchronously I would recommend you use The UIImageView category from AFNetworking which has been fully tested and is very well maintained.
However here you run into memory warnings on your device (which obviously holds much less memory than your simulator which runs on your Mac).
So I would use first the static analyzer:
to see if leaks are present and then run a Leaks Instrument to track it down.
Hope this helps.

defaultRepresentation fullScreenImage on ALAsset does not return full screen image

In my application I save images to an album as assets. I want also to retrieve them and display them in full screen. I use the following code :
ALAsset *lastPicture = [scrollArray objectAtIndex:iAsset];
ALAssetRepresentation *defaultRep = [lastPicture defaultRepresentation];
UIImage *image = [UIImage imageWithCGImage:[defaultRep fullScreenImage]
scale:[defaultRep scale] orientation:
(UIImageOrientation)[defaultRep orientation]];
The problem is that the image returned is nil. I have read at the ALAssetRepresentation reference that when the image does not fit it is returned nil.
I put this image to an UIImageView which has the size of the iPad screen. I was wondering if you could help me with this issue?
Thank you in advance.
I'm not a fan of fullScreenImage or fullResolutionImage. I found that when you do this on multiple assets in a queue, even if you release the UIImage immediately, memory usage will increase dramatically while it shouldn't. Also when using fullScreenImage or fullResolutionImage, the UIImage returned is still compressed, meaning that it will be decompressed before being drawn for the first time, thus on the main thread which will block your UI.
I prefer to use this method.
-(UIImage *)fullSizeImageForAssetRepresentation:(ALAssetRepresentation *)assetRepresentation
{
UIImage *result = nil;
NSData *data = nil;
uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*[assetRepresentation size]);
if (buffer != NULL) {
NSError *error = nil;
NSUInteger bytesRead = [assetRepresentation getBytes:buffer fromOffset:0 length:[assetRepresentation size] error:&error];
data = [NSData dataWithBytes:buffer length:bytesRead];
free(buffer);
}
if ([data length])
{
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceShouldAllowFloat];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailFromImageAlways];
[options setObject:(id)[NSNumber numberWithFloat:640.0f] forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
//[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailWithTransform];
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
if (imageRef) {
result = [UIImage imageWithCGImage:imageRef scale:[assetRepresentation scale] orientation:(UIImageOrientation)[assetRepresentation orientation]];
CGImageRelease(imageRef);
}
if (sourceRef)
CFRelease(sourceRef);
}
return result;
}
You can use it like this:
// Get the full image in a background thread
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage* image = [self fullSizeImageForAssetRepresentation:asset.defaultRepresentation];
dispatch_async(dispatch_get_main_queue(), ^{
// Do something with the UIImage
});
});

Array with UIImageView elements - setImageWithURL

I have such code
arrayWithImages = [[NSMutableArray alloc] init];
NSEnumerator *enumForNames = [arrayWithNames objectEnumerator];
NSEnumerator *enumForURLs = [arrayWithURLs objectEnumerator];
id objName, objURL;
while(objName = [enumForNames nextObject]) {
objURL = [enumForURLs nextObject];
UIImageView *anImage = nil;
[anImage setImageWithURL:[NSURL URLWithString:objURL]];
(...)
[arrayWithImages addObject:anImage];
}
And each time I got SIGABRT in line with "[arrayWithImages addObject:anImage];"
What's here wrong?
I don’t see an setImageWithURL method on UIImageView. Where is this from?
Is there any output from the SIGABRT crash?
Try this code:
// Download the image
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:objURL]];
// Make an UIImage out of it
UIImage *anImage = [UIImage imageWithData:imageData];
// Create an image view with the image
UIImageView *imageView = [[UIImageView alloc] initWithImage:anImage];
// Check to make sure it exists
if (imageView != nil) {
// Add it to your array
[arrayWithImages addObject:imageView];
else {
NSLog(#"Image view is nil");
}
Note that you should download the images asynchronously to avoid hanging the loop. This blog post discussing asynchronous image downloading.
Also if you know that [enumForURLs nextObject]; will return an NSString (or even better, NSURL) you should assign objURL to type NSString (or NSURL).

Create a UIImage with a URL in iOS

To create an UiImage with a image file, I use the code as below:
UIImage *aImage = [[UIImage imageNamed:#"demo.jpg"]autorelease];
If I want to create an UiImage with the URL http://example.com/demo.jpg, how to do that?
Thanks
UPDATE
This is a three step process. First you will create an NSURL object to hold the URL we are attempting to access. We will supply this URL to the NSData class method, +dataWithContentsOfURL: to obtain the image over the network as raw data, then use the +imageWithData: class method on UIImage to convert the data into an image.
NSURL *imageURL = [NSURL URLWithString:#"http://example.com/demo.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
Please note that +dataWithContentsOfURL: executes a synchronous network request. If you run this on the main thread, it will block the UI until the image data is received from the network. Best practice is to run any network code on a background thread. If you're targeting OS 4.0+ you could do something like this...
NSURL *imageURL = [NSURL URLWithString:#"http://example.com/demo.jpg"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
self.imageView.image = [UIImage imageWithData:imageData];
});
});
Here's what the same code might look like in Swift:
let image_url = NSURL("http://i.imgur.com/3yY2qdu.jpg")
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
let image_data = NSData(contentsOfURL: image_url!)
dispatch_async(dispatch_get_main_queue()) {
// update some UI
let image = UIImage(data: image_data!)
self.imageView.image = image
}
}
For anyone looking to load image from the web the following library may be helpful:
https://github.com/rs/SDWebImage
It's a UIImageView category which handles async loading and image caching from url.