Storing and retrieving images on iPhone efficiently - iphone

I have an application which donwloads several images and stores them on the phone. In total it will probably required around 20 images tops. I need to be able to retrieve any of these images at will depending on what screen the user is on. These images will be stored indefinitely, so I don't want to use temp directory.
At present I have a class named Images with these methods
- (void) cacheImage: (NSString *) ImageURLString : (NSString *)imageName
{
NSURL *ImageURL = [NSURL URLWithString: ImageURLString];
// Generate a unique path to a resource representing the image you want
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: imageName];
// Check for file existence
if(![[NSFileManager defaultManager] fileExistsAtPath: docFile])
{
// The file doesn't exist, we should get a copy of it
// Fetch image
NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
UIImage *image = [[UIImage alloc] initWithData: data];
// Is it PNG or JPG/JPEG?
// Running the image representation function writes the data from the image to a file
if([ImageURLString rangeOfString: #".png" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImagePNGRepresentation(image) writeToFile: docFile atomically: YES];
}
else if([ImageURLString rangeOfString: #".jpg" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[ImageURLString rangeOfString: #".jpeg" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImageJPEGRepresentation(image, 100) writeToFile: docFile atomically: YES];
}
}
}
- (UIImage *) getCachedImage : (NSString *)imageName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* cachedPath = [documentsDirectory stringByAppendingPathComponent:imageName];
UIImage *image;
// Check for a cached version
if([[NSFileManager defaultManager] fileExistsAtPath: cachedPath])
{
image = [UIImage imageWithContentsOfFile: cachedPath]; // this is the cached image
}
else
{
NSLog(#"Error getting image %#", imageName);
}
return image;
}
-(void)getImages
{
//example
NSString *image1URL = #"http://test/image1.png";
NSString *image2URL = #"http://test/image2.png";
NSString *image3URL = #"http://test/image3.png";
[self cacheImage:sLogo: #"Image1"];
[self cacheImage:sBlankNav: #"Image2"];
[self cacheImage:buttonLarge :#"Image3"];
}
-(void) storeImages
{
image1 = [self getCachedImage:#"Image1"];
image2 = [self getCachedImage:#"Image2"];
image3 = [self getCachedImage:#"Image3"];
}
So I use the code like this
Images *cache = [[Images alloc]init];
[cache storeImages];
The get images method is called once when the app first starts to get the images, it isn't called again after that, unless the images on the server are updated and I need to retrieve the updated ones.
The code works, but the problem is when I navigate to a screen that uses it, there is a very slight delay before the screen loads as it is loading the images.
My application is a tabbed application, so it begins on tab 1, I click tab 2 which implements the code, there will be a slight pause the first time it loads. It doesn't last very long, but it is noticeable and is very annoying. After that it is fine, as it is already loaded. However with navigation controller, every time you move from the first VC to the second VC, the method will be called again, so each time you navigate the delay will be there.
The images are not very big, biggest one is 68kb, others are much smaller than that. At present I am just testing with 5 images. Is there a more efficient way of storing and retrieving images, or am I doing something wrong with my code? I need to be able to retrieve these images without any noticeable delay in order for my application to remain fluid and not jerky or clunky.
Thanks in advance!!

You have two options to do the image loading work on a background thread - use Grand Central Dispatch or NSInvocationOperation. GCD might be considered the cleaner of the two:
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(q, ^{
//load images here
dispatch_async(main, ^{
// show on main thread here
});
});

you have delay because you're downloading data synchronously
// NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
Try some smart library like SDWebImage:
it lets you download image asynchronously while you still can display a local image (a proxy image). By the way, you still get cache image for free. So even if u are on local, you can still catch previously downloaded images
https://github.com/rs/SDWebImage
A must have

Related

Select image to use as UIViewController background throughout my app

When my iPhone app runs for the first time it asks you to select an image from your iPhone library to use as the background throughout your app. Here is the relevant code:
/*
* Responds when an image is selected (from browsing)
*/
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
[self dismissModalViewControllerAnimated:YES];
if ([mediaType isEqualToString:(NSString *)kUTTypeImage])
{
//Not really sure what to do here
UIImage *image = [[UIImage alloc] initWithData:UIImageJPEGRepresentation([info objectForKey:UIImagePickerController], .2)];
}
}
I now what to save this image (or a reference to this image) so that I can use it throughout my app as the background for some (60%) of my UIViewController
self.view.backgroundColor = [UIColor colorWithPatternImage:image];
What is the best/most efficient/easiest way to do this. I thought about saving the image into NSUserDefaults but this didn't seem right because NSUserDefaults is only supposed to be for small objects. Should I save it to disk? But then won't my app be really slow having to read from disk every time I segue? (If this is the proper way, how would I do this?)
Store your Image in Documents Directory
To save Image
- (void)saveImage:(UIImage *)image {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDirectory stringByAppendingPathComponent:#"savedImage.png"];
// Convert to Data
NSData *imageData = UIImagePNGRepresentation(image);
// write to file
[imageData writeToFile:savedImagePath atomically:NO];
}
To read Image use following
- (UIImage *)getImage {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *getImagePath = [documentsDirectory stringByAppendingPathComponent:#"savedImage.png"];
UIImage *img = [UIImage imageWithContentsOfFile:getImagePath];
return img;
}
Use a subclassed UIViewController where the background is always this selected background image, which you could have saved off using #Dipen's suggestions below (and +1 to him).
Literally the only thing that needs to exists in your new view controller is that in it's "viewDidLoad:" method, you add a subview that is a "UIImageView", set to your background image which you saved off as some preference.
You can then subclass further view controllers off your new "NosetrapViewController", which will automatically pick up the selected background image.

Iphone-saving full size image to document directory hangs my app

I am working on an app similar to photo app in iPhone. The flow is I get images through UIImagePickerController. When I use Actual image and further save that image to documents,
My app hangs. I saw on profile tool that allocation goes to 19 to 20 mb. If there is any solution to that.
Getting image from UIImagepicker controller
Saving it to documents.
-(NSString *)saveImageInDocumentsAtPath:(NSNumber *)number
{
NSString *fileName=[NSString stringWithFormat:#"image%#.png",number];
NSString *imagePath= [[NSString alloc] initWithFormat:#"%#/%#", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0],fileName];
UIImage *image = self.selectedImage.image;
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:imagePath atomically:YES];
return imagePath;
}
A typical image is going to be around 3Megabytes, if I am not mistaken (depending on the iPhone and its camera). So going from 19 to 20 is not unreasonable. Why you are at 19 to begin with ay be another question. See if you're loading in too many other images, large files etc.
If you use AV Foundation instead of ImagePicker, you have some other options for handling the images.
And you can write the image to the filesystem asynchronously.
This looks like a great opportunity for some error checking in your code.
Why not try this?
-(NSString *)saveImageInDocumentsAtPath:(NSNumber *)number
{
NSString *fileName=[NSString stringWithFormat:#"image%#.png",number];
NSArray * docDirectoryArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if(docDirectoryArray)
{
NSString *imagePath= [[NSString alloc] initWithFormat:#"%#/%#", [docDirectoryArray objectAtIndex:0],fileName];
UIImage *image = self.selectedImage.image;
if(image)
{
NSData *imageData = UIImagePNGRepresentation(image);
if(imageData)
{
BOOL success = [imageData writeToFile:imagePath atomically:YES];
if(success)
{
// only return the image path in the success == YES case
return imagePath;
} else {
NSLog( #"did not save file to %#", imagePath);
}
} else {
NSLog( #"could not get image data out of the image");
}
} else {
NSLog( #"no image selected");
}
}
// you might want to check in the caller to make certain the imagePath is NULL
return NULL;
}

Corrupted Images in document directory

I am fetching URL of the images from the server and converting this images into png format using:
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(img)];
[data1 writeToFile:pngFilePath atomically:YES];
but some of the images are corrupted when i check them after completing the process on simulator.
Hence these images are not displaying on the app wherever needed.
Please see the attached image as some images are corrupted.
Update
I am calling a method in a loop which fetches the images from the server parallel and didFinishLoading I am performing this:
UIImage *img = [UIImage imageWithData:self.data];
NSArray *split = [self.strImageName componentsSeparatedByString:#"/"];
int arrCount=[split count];
NSString *imageName=[split objectAtIndex:arrCount-1];
NSString *docDirec = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *pngFilePath=nil
pngFilePath = [NSString stringWithFormat:#"%#/Thumbs/%#",docDirec,imageName];
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(img)];
[data1 writeToFile:pngFilePath atomically:YES];
[self.data release]; //don't need this any more, its in the UIImageView now
self.data=nil;
i have the similar problem
i solved it using the below code
- (void)viewWillDisappear:(BOOL)animated
{
if (self.m_objWebManager != nil)//Webmanager is class for downloading the images as a background thread
{
[self.m_objWebManager cancelCommunication];
[self.m_objWebManager release];
self.m_objWebManager = nil;
}
}

Image storage as byte code locally from XML parsing & display if it is stored Otherwise go for parsing.

I am having an app in which at the time of launcing the app XML parsing is giving Main category from URL like hp, dell, etc...I am displaying it in the Tableview.
Then on click of particular cell i can get the detail of main category means its subcategory like http://www.dealsbell.com/findcoupon.php?store=hp
Here also i am getting data properly after parsing.
But my concern over here is, in ( http://www.dealsbell.com/findcoupon.php?store=hp ) this link i am getting images.
Each particular subcategory will have a same image. So i want to do something like that the image if first time loaded from the URL then it will display image from parsing otherwise i would like to store that image as its byte code in folder / file / in any way in my device on first parsing.
If once the image is stored to the particular way in my device next time when i will go to see the subcategory it will first check this image is stored locally to my device or not.
If yes then it should go to the particular location to fetch this local image & display it to each cell otherwise will parse & display image.
I hope you are getting, what i want to ask.
Please guide me, how can this be possible & what is the way to get result.
If any example or link you can suggest, then it will be more efficient to me.
Thanks in advance.
There are probably two ways to achive this.
Get NSData out of Image and hold that in UserDefaults or database.
Dump image in application folder and pick image from that place.
So whenever you try to load image for subcatogory check at one of place and if present use that. IF in case you have stored image and if any updated image comes,then remove previous copy and store new one.
-(void) SaveImageinDocumentWithName:(UIImage*) aUIImage Name:(NSString*) aName
{
if(aUIImage)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableString* str = [[NSMutableString alloc] initWithCapacity:300];
[str appendString:documentsDirectory];
[str appendString:#"/"];
[str appendString:aName];
NSFileManager *fileManager = [NSFileManager defaultManager];
[UIImagePNGRepresentation(aUIImage) writeToFile:str atomically:YES];
if(str)
{
[str release];
str = nil;
}
if(fileManager)
{
[fileManager release];
fileManager = nil;
}
[pool release];
}
}
-- Getting saved image
-(UIImage*)GetSavedImageWithName:(NSString*) aFileName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableString* str = [[NSMutableString alloc] initWithCapacity:300];
[str appendString:documentsDirectory];
[str appendString:#"/"];
[str appendString:aFileName];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success = [fileManager fileExistsAtPath:str];
NSData *dataToWrite = nil;
UIImage* image = nil;
if(!success)
{
}
else
{
image = [[UIImage alloc] initWithContentsOfFile:str];
}
if(dataToWrite)
{
[dataToWrite release];
dataToWrite = nil;
}
if(str)
{
[str release];
str = nil;
}
if(fileManager)
{
[fileManager release];
fileManager = nil;
}
return image;
}
Parse dealsbell.com/wp-content/uploads/mobile/hp.gif and take only hp.gif
NSString *strImage = [NSString stringWithFormat:#"%#",aBook.image];
UIImage* image = [self GetSavedImageWithName:strImage];
if(image) // This means Image exists
{
// Do what you want
}
else
{
NSURL *url4Image = [NSURL URLWithString:strImage];
NSData *data = [NSData dataWithContentsOfURL:url4Image];
if(data != NULL)
{
image =[[UIImage alloc] initWithData:data];
[self SaveImageinDocumentWithName:image Name:strImage]; // save for future ref.
}
else
{
image =[UIImage imageNamed:#"icon.png"];
}
}

How to save an image that is returned by UIImagePickerView Controller

I have an application, in which the user will select an image from a UIImagePickerView.
After selecting an image from it, I want to save it in my application.
How can this be achieved?
Thanks in advance for helping me.
Assuming you're using SDK 3.0, here is some code to save the image into your application's documents folder:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// Dismiss the picker
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
// Get the image from the result
UIImage* image = [info valueForKey:#"UIImagePickerControllerOriginalImage"];
// Get the data for the image as a PNG
NSData* imageData = UIImagePNGRepresentation(image);
// Give a name to the file
NSString* imageName = "MyImage.png";
// Now, we have to find the documents directory so we can save it
// Note that you might want to save it elsewhere, like the cache directory,
// or something similar.
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
// Now we get the full path to the file
NSString* fullPathToFile = [documentsDirectory stringByAppendingPathComponent:imageName];
// and then we write it out
[imageData writeToFile:fullPathToFile atomically:NO];
return;
}
I would say something like this:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
self.resumePreviousSettingAfterEditing = true;
[self.topImageView setImage:image];
[cubeModel setImage:image forFace:[cubeModel.validFaces objectAtIndex:selectedRowInFacePicker]];
[self dismissImagePickerAnimated:true];
}
You register an event in your controller to handle the image selection. In that event handler, call a method somewhere, say in your model to set the new image. That function would look something like this:
(void)saveImage:(UIImage *)image withName:(NSString *)imageName {
// get the image path
NSString *filename = [self determineImagePath:imageName];
// make sure the file is removed if it exists
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:filename]) {
if(NO == [fileManager removeItemAtPath:filename error:NULL]) {
}
}
// Now, save the image to a file.
if(NO == [UIImagePNGRepresentation(image) writeToFile:filename atomically:YES]) {
[NSException raise:#"Image Save Failed" format:#"Unable to store image %s", filename];
}
}
When you want to load the image again, you would so something like:
- (UIImage *)loadImage:(NSString *)imageName {
NSString *filename = [self determineImagePath:imageName];
NSFileManager *fileManager = [NSFileManager defaultManager];
self.currentImage = nil;
if([fileManager fileExistsAtPath:filename]) {
NSData *imageData = [[NSData alloc] initWithContentsOfFile:filename];
UIImage *image = [UIImage imageWithData:imageData];
self.currentImage = image;
}
return self.currentImage;
}
And don't get me started on transforming which is way harder than it should be.
Enjoy,
Jacob
One thing you will need to address when saving images returned by UIImagePickerVIewController is that writing the data to disk will almost always be unacceptably slow. Your UI will hang while the writing is occurring. So, you should always execute these types of operations in an asynchronous queue. Even if the performance seems good enough for your application when testing, you should still do it an asynch queue -- you never know what other processes the device might have going on which might slow the save down once your app is in the hands of users.
Newer versions of iOS make saving photos asynchronously really, really easy using Grand Central Dispatch (GCD). The steps are:
Create an NSBlockOperation which saves the image
In the block operation's completion block, read the image from disk & display it (the only caveat here is that you must use the main queue to display the image: all UI operations must occur on the main thread).
Add the block operation to an operation queue and watch it go!
That's it. And here's the code:
// Grab the image
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
// Create a block operation with our saves
NSBlockOperation* saveOp = [NSBlockOperation blockOperationWithBlock: ^{
[UIImagePNGRepresentation(image) writeToFile:file atomically:YES];
}];
// Use the completion block to update our UI from the main queue
[saveOp setCompletionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImage *image = [UIImage imageWithContentsOfFile:file];
// TODO: Assign image to imageview
}];
}];
// Kick off the operation, sit back, and relax. Go answer some stackoverflow
// questions or something.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:saveOp];
Once you are comfortable with this code pattern, you will find yourself using it a lot. It's incredibly useful when generating large datasets, long operations on load, etc. Essentially, any operation that makes your UI laggy in the least is a good candidate for this code. Just remember, you can't do anything to the UI while you aren't in the main queue and everything else is cake.