How to efficiently read multiple images from documents directory in iOS - objective-c

I am working on an app that saves multiple images in Documents directory. These images can be up to 100. Now use following method to read the image from Documents directory. This method is called for all images in Documents directory.
UIImage *currentImage = [UIImage imageWithContentsOfFile:pathOfFileInDocumentsDictory];
So in worse case this method will run for 100 images and I have checked using XCode that this method takes around 100 miliseconds. So this makes 10 seconds for 100 images if I am not wrong. I want to make it efficient. Is there any better way to read those image for efficiently and in less time?

Using run loops, you could do this:
-(void) loadInBackground {
[self performSelectorInBackground:#selector(_loadInBackground) withObject:nil];
}
-(void) _loadInBackground {
// Do all your heavy loading here
UIImage *currentImage = [UIImage imageWithContentsOfFile:pathOfFileInDocumentsDictory];
[self performSelectorOnMainThread:#selector(loadedImage:) withObject:currentImage waitUntilDone:YES];
}
-(void) loadedImage:(UIImage*)img {
// Do something with the loaded image
anImageView.image = img;
}

Related

Load image using imageWithContentsOfFile + GCD in table still slower than SDWebImage

I have been using SDWebImage's setImageWithURL... to load remote images on tableview. It is perfect.
Now I need to create a table of user saved images but since images are not remote, I cant use SDWebImage. Besides images are saved by user using [imageData writeToFile..] so I cant use imageNamed either.
I tried to use GCD (this code is part of a UIImageView+ category..):
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
dispatch_sync(dispatch_get_main_queue(), ^{
[self setImage:image];
[self setNeedsLayout];
});
});
It still feels much slower than a table of remote images managed by SDWebImage. Is there any way to handle cache/asynchronous loading of LOCAL images?

Received memory warning but the live bytes are only 5 to 10MB [duplicate]

This problem has completely stumped me. This is for iOS 5.0 with Xcode 4.2
What's going on is that in my app I let user select images from their photo album and I save those images to apps document directory. Pretty straight forward.
What I do then is that in one of the viewController.m files I create multiple UIImageViews and I then set the image for the image view from one of the picture that user selected from apps dir. The problem is that after a certain number of UIImage sets I receive a "Received memory warning". It usually happens when there are 10 pictures. If lets say user selected 11 pictures then the app crashes with Error (GBC). NOTE: each of these images are at least 2.5 MB a piece.
After hours of testing I finally narrowed down the problem to this line of code
[button1AImgVw setImage:image];
If I comment out that code. All compiles fine and no memory errors happen. But if I don't comment out that code I receive memory errors and eventually a crash. Also note it does process the whole CreateViews IBAction but still crashes at the end. I cannot do release or dealloc since I am running this on iOS 5.0 with Xcode 4.2
Here is the code that I used. Can anyone tell me what did I do wrong?
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self CreateViews];
}
-(IBAction) CreateViews
{
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask ,YES);
documentsPath = [paths objectAtIndex:0];
//here 15 is for testing purposes
for (int i = 0; i < 15; i++)
{
//Lets not get bogged down here. The problem is not here
UIImageView *button1AImgVw = [[UIImageView alloc] initWithFrame:CGRectMake(10*i, 10, 10, 10)];
[self.view addSubview:button1AImgVw];
NSMutableString *picStr1a = [[NSMutableString alloc] init];
NSString *dataFile1a = [[NSString alloc] init];
picStr1a = [NSMutableString stringWithFormat:#"%d.jpg", i];
dataFile1a = [documentsPath stringByAppendingPathComponent:picStr1a];
NSData *potraitImgData1a =[[NSData alloc] initWithContentsOfFile:dataFile1a];
UIImage *image = [[UIImage alloc] initWithData:potraitImgData1a];
// This is causing my app to crash if I load more than 10 images!
// [button1AImgVw setImage:image];
//If I change this code to a static image. That works too without any memory problem.
button1AImgVw.image = [UIImage imageNamed:#"mark-yes.png"]; // this image is less than 100KB
}
NSLog(#"It went to END!");
}
This is the error I get when 10 images are selected. App does launch and work
2012-10-07 17:12:51.483 ABC-APP[7548:707] It went to END!
2012-10-07 17:12:51.483 ABC-APP [7531:707] Received memory warning.
App crashes with this error when there are 11 images
2012-10-07 17:30:26.339 ABC-APP[7548:707] It went to END!
(gbc)
This situation (memory warnings and application quitting while attempting to load multiple full resolution UIImages into a view) has attempted to burn me a couple times in my iOS programming career.
You need to make a shrinked down copy of your original image before doing the "setImage" call.
For my own code, I use the "UIImage+Resize" category, the details for which can be found here.
Resize your image to something smaller before inserting into your view, then make sure the full resolution image is released (or set to nil if on ARC) and you should have a happier time of things.
Here is how I do it in my own code:
CGSize buttonSize = CGSizeMake(width, height);
// it'd be nice if UIImage took a file URL, huh?
UIImage * newImage = [[UIImage alloc] initWithContentsOfFile: pathToImage];
if(newImage)
{
// this "resizedimage" image is what you want to pass to setImage
UIImage * resizedImage = [newImage resizedImage: buttonSize interpolationQuality: kCGInterpolationLow];
}

Xcode: Array of images fails to instantiate on device

I've got an app that's working perfectly on the simulator, but fails on my iPod Touch (4th gen), and I'd like to know why. The part that's failing is a simple interactive menu that shows six pictures on the root of a UINavigationController, then pushes a viewController which instantiates an array of food images, creates a view which holds all the images side-by-side, and moves the viewing area over the image that correlates to the image clicked in the root view. When I run it on the device, the array only instantiates with pointers to two images, and an exception is thrown when the array is used to create the images side-by-side.
//code from the pushed view controller
- (void)setupScrollView:(UIScrollView*)scrMain {
// we have 6 images here.
// we will add all images into a scrollView & set the appropriate size.
NSMutableArray *array = [NSArray arrayWithObjects:
[UIImage imageNamed:#"shrimpquesadilla.jpg"],
[UIImage imageNamed:#"pulledpork.jpg"],
[UIImage imageNamed:#"filetMignon.jpg"],
[UIImage imageNamed:#"Reuben.jpg"],
[UIImage imageNamed:#"cajunshrimp.jpg"],
[UIImage imageNamed:#"primerib.jpg"], nil];
NSLog(#"stuff: %#", array);
for (int i=1; i<=6; i++) {
UIImage *image = [array objectAtIndex:(i-1)];
UIImageView *imgV = [[UIImageView alloc]
initWithFrame:CGRectMake((i-1)*scrMain.frame.size.width,
0, scrMain.frame.size.width, (scrMain.frame.size.height - 90))];
imgV.contentMode=UIViewContentModeScaleToFill;
[imgV setImage:image];
imgV.tag=i+1;
[scrMain addSubview:imgV];
}
[scrMain setContentSize:CGSizeMake(scrMain.frame.size.width*6,
scrMain.frame.size.height)];
[scrMain scrollRectToVisible:CGRectMake(self.count*scrMain.frame.size.width,
0, scrMain.frame.size.width, scrMain.frame.size.height) animated:YES];
}
The output of the NSLog when run through the simulator:
2012-08-20 09:51:23.812 DemoTabbed[1545:11603] stuff: (
"<UIImage: 0x7931150>",
"<UIImage: 0x6e63270>",
"<UIImage: 0x6e67700>",
"<UIImage: 0x6e68040>",
"<UIImage: 0x6e5c700>",
"<UIImage: 0x6e64210>"
)
And the output when run on the device:
2012-08-20 10:26:50.211 DemoTabbed[2128:707] stuff: (
"<UIImage: 0x197e20>",
"<UIImage: 0x181270>"
)
And then there's the standard error for the index out of bounds. I don't know if it's relevant or not, but two of my icons aren't loading on the device, either, though they work on the simulator. Let me know if you need any more code, or if you have questions about the app or its behavior, I'd be happy to add more.
EDIT: I have tried rearranging the order in which the images are instantiated into the array, and nothing was changed by it. The output stll showed that only two images were pointed to by the array.
iOS has a case-sensetive file system. You have a case problem in #"filetMignon.jpg" file, making it resolve to nil image and ending the array elements early.
To fix that, make sure that images are named in same case as you load them (the better idea would be to always have a lowercase image names).
It's not an issue on Simulator, as OS X is using case-insensetive (in 99% cases) filesystem, meaning, #"filetMignon.jpg" and #"filetmignon.jpg" would resolve to same file.

How To Load Images From NSHomeDirectory

So, I'm currently saving images in my UIImageView via this method:
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSString *image1 = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents/image1.png"];
NSString *image2 = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents/image2.png"];
}
The images save just fine, but I have no idea how to set the UIImageViews images in viewDidLoad. This is what I've been trying:
- (void)viewDidLoad
{
self.imageView.image =[UIImage imageNamed:[(NSHomeDirectory *)Documents objectForKey:#"image1"]];
self.imageView2.image =[UIImage imageNamed:[(NSHomeDirectory *)Documents objectForKey:#"image1"]];
}
But, obviously that is not working. I'm having trouble understanding the basics here. Any help would be great, thanks!
The -imageNamed: method looks in the application's main bundle if the named image hasn't been cached yet. From the docs:
The name of the file. If this is the first time the image is being loaded, the method looks for an image with the specified name in the application’s main bundle.
(See UIImage class reference)
You want -imageWithContentsOfFile: instead.

Should I return a static UIImage or return a copy?

In a class named Utilities, I'm using a single static image to load up all the buttons in view, like this:
static UIImage *baseImage_bottomToolbarBG;
+ (void) initialize {
baseImage_bottomToolbarBG =
[UIImage imageNamed:#"bottom-toolbar-background"];
// take bottom-toolbar-background, which is huge, copy it down to size
// at which it will actually be used
}
+ (void) dealloc {
baseImage_bottomToolbarBG = nil;
}
+ (UIImage *)getBottomToolbarImagePortrait {
return baseImage_bottomToolbarBG;
}
... and then to set up a button using this background image, I call this:
UIImage *image = [Utilities getBottomToolbarImage];
[button setBackgroundImage:image forState:UIControlStateNormal];
Is this an OK practice, or in my getBottomToolbarImage should I make a copy of the static UIImage and return the copy?
This is using ARC and targeting iOS 4+
Edit: see new comment in initialize method
It would be better to just ship a properly-scaled image as part of your application and always use imageNamed: to fetch it, because imageNamed: handles caching for you.
If you really don't want to do that for some reason, then your code is ok. There's no reason to create additional copies of the image.
However, class objects never get destroyed, so you don't need the dealloc method. In fact I would do it all in one method, like this:
+ (UIImage *)getBottomToolbarImagePortrait {
static dispatch_once_t once;
static UIImage *image;
dispatch_once(&once, ^{
image = [UIImage imageNamed:#"bottom-toolbar-background"];
// Do [image retain] here if not using ARC
// image scaling here
});
return image;
}
I believe the better way to do this would be not to keep a static copy of the image around. Instead, create a static method like this:
+ (UIImage*)bottomToolbarImagePortrait {
return [UIImage imageNamed:#"bottom-toolbar-background.png"];
}
The static class doesn't need to worry about memory management for the object; the calling class will need to take care of that itself.
A static UIImage could be changed in one place and unexpectedly affect another object trying to use the same image. The above method avoids this.
EDIT
In light of the fact that you're modifying the image and using that modified image in several places, I would implement your getter like this:
+ (UIImage*)bottomToolbarImagePortrait {
return [UIImage imageWithCGImage:[baseImage_bottomToolbarBG CGImage]];
}
Again, you don't want to keep using the same image as it may get modified unexpectedly in one place and affect another. The above makes a copy of the image and returns it.
Unfortunately, [baseImage_bottomToolbarBG copy] does not work as one would hope since UIImage does not conform to NSCopy.