Cache Image From URL working, but returns blank image? - objective-c

I have two methods, first checks if I've already downloaded the image, and if not retrieves the image from a URL and caches it to my docs directory in my app. If it has been, it simply retrieves it, and if I have a internet connection, will re-download it. Here are the two methods:
- (UIImage *) getImageFromUserIMagesFolderInDocsWithName:(NSString *)nameOfFile
{
UIImage *image = [UIImage imageNamed:nameOfFile];
if (!image) // image doesn't exist in bundle...
{
// Get Image
NSString *cleanNameOfFile = [[[nameOfFile stringByReplacingOccurrencesOfString:#"." withString:#""]
stringByReplacingOccurrencesOfString:#":" withString:#""]
stringByReplacingOccurrencesOfString:#"/" withString:#""];
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"Documents/%#.png", cleanNameOfFile]];
image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:filePath]];
if (!image)
{
// image isn't cached
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nameOfFile]]];
[self saveImageToUserImagesFolderInDocsWithName:cleanNameOfFile andImage:image];
}
else
{
// if we have a internet connection, update the cached image
/*if (isConnectedToInternet) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nameOfFile]]];
[self saveImageToUserImagesFolderInDocsWithName:cleanNameOfFile andImage:image];
}*/
// otherwise just return it
}
}
return image;
}
Here's to save the image
- (void) saveImageToUserImagesFolderInDocsWithName:(NSString *)nameOfFile andImage:(UIImage *)image
{
NSString *pngPath = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"Documents/%#.png", nameOfFile]];
[UIImagePNGRepresentation(image) writeToFile:pngPath atomically:YES];
NSLog(#"directory: %#", [[UIImage alloc] initWithContentsOfFile:pngPath]);
}
The image has already been successfully downloaded and cached to my documents directory (I know because I can see it in the File system). And it successfully re loads the image the first time I call this method, but once I go to another view, and re-call this method when I come back to the same view, it's blank. Yet, the URL is correct. What's wrong here?

1) You should not be writing into a hardcoded path as you do (ie "Documents/xxx"), but rather ask for the Application Support directory, use it, and also mark files so that they don't get uploaded to iCloud (unless you want that). See this link on the specifics. Create a subfolder in it and mark it as not for iCloud backup.
2) Try changing:
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nameOfFile]]];
to
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:filePath]]];

Maybe, you should do:
image = [UIImage imageWithContentsOfFile: nameOfFile];

Related

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.

NSURL of UIImage from UIImagePickerController returns (null)

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editInfo {
userURL = [editInfo objectForKey:UIImagePickerControllerMediaURL];
userImage = image;
userImageView.image=userImage;
[self dismissViewControllerAnimated:YES completion:nil];}
I then take NSURL userURL, and put it in an UIActivityViewController to use to upload the image. However this never works, and always fails as it's trying to upload (null). However, when I use a preset image included in the xcode project and the following code, it always works and uploads correctly:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"kitten.jpg" withExtension:nil];
If it helps, I'm using https://github.com/goosoftware/GSDropboxActivity
When I use UIImagePickerControllerReferenceURL instead of UIImagePickerControllerMediaURL, I get the following error:
[WARNING] DropboxSDK: File does not exist (/asset.JPG)
Failed to upload assets-library://asset/asset.JPG?id=EECAF4D0-A5ED-40E7-8E6F-3A586C0AB06E&ext=JPG
In theory, UIImagePickerControllerMediaURL is only populated for a movie, not an image. If you use the UIImagePickerControllerReferenceURL then the URL that you're given is not a URL for the file system, but rather the assets library. To get the file from that you'll need to use the asset library. Use something like the following :
typedef void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *asset);
typedef void (^ALAssetsLibraryAccessFailureBlock)(NSError *error);
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset){
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref){
UIImage *myImage = [UIImage imageWithCGImage:iref scale:[rep scale] orientation:(UIImageOrientation)[rep orientation]];
// Do whatever you now want with your UIImage
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror){
//failed to get image.
};
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:[filePath objectAtIndex:0] resultBlock:resultblock failureBlock:failureblock];
This takes care of the blocks that will handle your request, but you still need to actually request the resource from the assets library. For that, try this :
ALAssetsLibrary* assetslibrary = [[[ALAssetsLibrary alloc] init] autorelease];
NSURL myAssetUrl = [NSURL URLWithString:[editInfo objectForKey:UIImagePickerControllerMediaURL]];
[assetslibrary assetForURL:myAssetUrl resultBlock:resultblock failureBlock:failureblock];
And in theory, you should get what you're after :)
The code for this was given by Ramshad and further discussion of it can be found here
Hopefully this will help you with what you're after. Sorry if it's a bit too late :(
Edit
Note that if you're not using ARC, you'll need to tidy up the memory in this example as I haven't included any memory management at all.

Loading an image from a URL but displaying it progressively

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

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).

Instagram open UTI directly

I recently stumbled upon the following interesting feature: Instagram iPhone Hooks
I was wondering if one is able to open an app through the documentinteractioncontroller immediately, WITHOUT having to show a preview (– presentPreviewAnimated:) or an action sheet (– presentOpenInMenuFromRect:inView:animated:). Seems to me that it's the only way, but I might be missing something.
-(void) saveToInstagram {
NSURL *url;
UIDocumentInteractionController *dic;
CGRect rect = CGRectMake(0 ,0 , 0, 0);
UIImage *i = imageView.image;
CGRect cropRect = CGRectMake(i.size.width - 306 ,i.size.height - 306 , 612, 612);
NSString *jpgPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents/Test.ig"];
CGImageRef imageRef = CGImageCreateWithImageInRect([imageView.image CGImage], cropRect);
UIImage *img = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);
[UIImageJPEGRepresentation(img, 1.0) writeToFile:jpgPath atomically:YES];
url = [[NSURL alloc] initFileURLWithPath:jpgPath];
dic = [self setupControllerWithURL:url usingDelegate:self];
[dic presentOpenInMenuFromRect:rect inView:self.view animated:YES];
[dic retain];
[img release];
[url release];
}
- (UIDocumentInteractionController *) setupControllerWithURL: (NSURL*) fileURL usingDelegate: (id <UIDocumentInteractionControllerDelegate>) interactionDelegate {
UIDocumentInteractionController *interactionController = [UIDocumentInteractionController interactionControllerWithURL: fileURL];
interactionController.delegate = interactionDelegate;
return interactionController;
}
- (void)documentInteractionControllerWillPresentOpenInMenu:(UIDocumentInteractionController *)controller {
}
First save your JPEG with a .ig or .igo extension instead of .jpg or .jpeg then create a NSURL with a file:// directive. Also I used the .igo extension and the UTI com.instagram.exclusivegram for exclusivity to Instagram but you can use the .ig extension and the UTI com.instagram.gram
For Example:
// URL TO THE IMAGE FOR THE DOCUMENT INTERACTION
NSURL *igImageHookFile = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:#"file://%#", jpgPath]];
docInteractionController.UTI = #"com.instagram.exclusivegram";
[self setupDocumentControllerWithURL:igImageHookFile];
// OPEN THE HOOK
[docInteractionController presentOpenInMenuFromRect:self.frame inView:self animated:YES];
The only way to do it would be if Instagram registers a custom URL handler (e.g. igram://) and can accept data either as part of that URL (e.g. base64 encoded) or via the pasteboard.
Basically the limitations that UIDocumentInteractionController (and this technique) workaround are:
Apps are not allowed to query for or directly launch other apps.
Apps cannot generally open files from the sandboxed user directory of other apps.