I have developed an iOS7 app. I use the UIImagePickerController to get a UIImage. During the usage of the app an image
is stored to the local app directory and loaded. The file will be overwritten several times. I use UIImagePNGRepresentation and NSData storeToFile to store
the picture. In order to load the image I use UIImage initWithContentsOfFile.
It works fine for some store and load processes. With the Debugger I can confirm that the picture is stored and loaded appropriately.
But after some time the stored picture does not contain a picture anymore. When I look at the load procedure with the debugger I can see that
a picture is loaded but it seems to be completely transparent with no other information.
This phenomenon only occurs on a device and not during a simulation with the simulator. Furthermore, the effect seems to occur only when the app is kicked out of the memory (double tap of the home button)
I debugged the app for hours but I cannot imagine the reasons for this behaviour. Additionally it is difficult to debug due to the complete termination of the app. Does anybody know a solution or has been faced with the same problem? Could anybody give me a hint what to debug to track down the problem?
Thanks in advance. Any help is appreciated. :)
Thanks for your answers. Here is a code excerpt:
Code to store image:
Generate file path in documents directory
NSString *name = [NSString stringWithFormat:#"ImageforButton%i.png",i];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *file = [[path objectAtIndex:0] stringByAppendingPathComponent:name];
Create NSData from UIImage (image is a UIImage that always has the correct image data)
NSData *picData = UIImagePNGRepresentation(image);
Write NSData to file
[picData writeToFile:file atomically:true];
Code to load image:
Generate file path
NSString *name = [NSString stringWithFormat:#"ImageforButton%i.png",i];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *file = [[path objectAtIndex:0] stringByAppendingPathComponent:name];
Load picture
UIImage *img;
if ([[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:false] == true)
{
img = [UIImage imageWithContentsOfFile:[self saveFilePath:name]];
}
img does not always contain the right data. Before the app is completely terminated img
contains the expected picture. After the app was completely terminated a valid
UIImage can be loaded to img but it seems only to contain a transparent picture.
All in all it does not contain the right picture anymore.
Related
Recently we have updated iOS 13. Earlier to the update our app was able to pick videos/media from Gallery. But lately after new update, the app picks the video from gallery but once it is uploaded, the uploaded content is shown as 0 bytes. Guessing that OS is not giving access to Gallery from our app.
In iOS 12 and prior versions we didn't see this issue. Gallery was working fine with our App.
Below delegate method returns different URLs in iOS 13 and earlier iOS versions.
- (void)videoPickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];}
I am returning Video URL path from the UIImagePickerController.
iOS 13 returns below value
file:///private/var/mobile/Containers/Data/PluginKitPlugin/36B89CF8-26FC-44BA-AFE1-D689DC04AF44/tmp/trim.400D33F1-BA28-42C8-B6B7-5CEC26656917.MOV
Earlier iOS versions returns below value
file:///private/var/mobile/Containers/Data/Application/7BFD7CEC-E383-4C63-8DF8-A0830EE948AC/tmp/36346EAD-AADC-4A9A-9DEE-416A2DE0BE71.MOV
Note: When the video recorded in camera directly from app is uploaded. It doesn't throw us any issues. It is when the video picked from Gallery then the app behaves differently.
Please share your suggessions.
Thanks in advance.
I had a similar issue. I think the solution is to copy the video to a temporary location that your App has access to.
The answer from another question is here.
Solution for above issue: in iOS13 after dismiss the video album, video URL is getting invalide. For this we need to change URL Directory Path.
NSURL *videoURL1 = [info objectForKey:UIImagePickerControllerMediaURL];
NSMutableData *imageData = [NSMutableData dataWithContentsOfURL:videoURL1];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *videoPath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#",[NSString stringWithFormat:#"%u.MOV",arc4random() % 99999]]];
BOOL success = [imageData writeToFile:videoPath atomically:NO];
NSURL *videoURL = [NSURL fileURLWithPath:videoPath];
Hi I'm new to iOS objective c.
I'm working on a app which fetches images and stores it in the app memory for reference.
when u click on the favourite it should go the favourites tab
when clicked on downloads.The image should go on the download tab.
as ALAasset is deprecated can some one explain what is the best way to store images.
is it using file or PHImagemanager.
and how?
You should store images in documents directory something like,
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
NSString *documentsDirectoryForSaveImages = [docsDir stringByAppendingPathComponent:#"/ProfileImages"];
[[NSFileManager defaultManager] createDirectoryAtPath:documentsDirectoryForSaveImages withIntermediateDirectories:YES attributes:nil error:nil]; // This creates folder in document directory of name "ProfileImages"
Now you can store image something like,
NSData *data = UIImageJPEGRepresentation(responseObject, 1.0); //Convert image to data
[data writeToFile:[documentsDirectoryForSaveImages stringByAppendingPathComponent:formImage] atomically:NO]; //write that data to path. formImage is string value (name of image. Ideally should use timestamp to make unique naming)
You can retrieve image something like,
UIImage *img = [UIImage imageWithContentsOfFile:[documentsDirectoryForSaveImages stringByAppendingPathComponent:formImage]];
Hope this will help :)
So, my app queries an Amazon Dynamo DB database and retrieves a few kilobytes worth of data. What I want the app to do is download everything the first time, and then every time after, just download a timestamp to see if it has the most recent version of the data. So that I only have to download the data every once in a while, I'm trying to use NSKeyedArchiver to archive the array that I'm downloading. I have tried this three different ways, and none of them work on an iPhone, although two of them work on the simulator.
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"dataArray.archive"];
This does not work on the simulator nor the actual iphone. The result of this method is NO.
The next thing I used was the full path:
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"Users/Corey/Desktop/.../dataArray.archive"];
And this worked on the simulator, but not on the iPhone. My guess was that when compiled, the filesystem looks different (and obviously doesn't have the same path). So next I tried:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"dataArray" ofType:#".archive"];
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:filePath];
Once again, this works on the simulator but fails on the iphone. I have confirmed that all of the data is in self.dataArray before writing to the archive, and confirmed that the array is nil after writing back to the archive (in the iphone version). Any ideas what's going on? Is there a better way to do the filepath?
This is what I tracked down:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent: #"dataArray.archive"];
[NSKeyedArchiver archiveRootObject:your_object toFile:filePath];
and it worked perfectly on both the simulator and the iPhone!
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"dataArray.archive"];
You have to provide a full path.
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"Users/Corey/Desktop/.../dataArray.archive"];
That is not a full path. A full path begins with / and does not have /../ anywhere.
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"dataArray" ofType:#".archive"];
You do not have permission to write inside the mainBundle, it is read only.
Also, in general you shouldn't use file paths, you should use URLs. Some APIs (including this one) requires a path but URLs are the recommended approach these days.
Here's the proper way to write the file to disk:
NSURL *applicationSupportUrl = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask][0];
applicationSupportUrl = [applicationSupportUrl URLByAppendingPathComponent:#"My App"]; // replace with your app name
if (![applicationSupportUrl checkResourceIsReachableAndReturnError:NULL]) {
[[NSFileManager defaultManager] createDirectoryAtURL:applicationSupportUrl withIntermediateDirectories:YES attributes:#{} error:NULL];
}
NSURL *archiveUrl = [applicationSupportUrl URLByAppendingPathComponent:#"foo.archive"];
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:archiveUrl.path];
From profiling with Instruments I have learned that the way I am saving images to disk is resulting in memory spikes to ~60MB. This results in the App emitting low memory warnings, which (inconsistently) leads crashes on the iPhone4S running iOS7.
I need the most efficient way to save an image to disk.
I am currently using this code
+ (void)saveImage:(UIImage *)image withName:(NSString *)name {
NSData *data = UIImageJPEGRepresentation(image, 1.0);
DLog(#"*** SIZE *** : Saving file of size %lu", (unsigned long)[data length]);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:name];
[fileManager createFileAtPath:fullPath contents:data attributes:nil];
}
Notes:
Reducing the value of the compressionQuality argument in UIImageJPEGRepresentation does not reduce the memory spike significantly enough.
e.g.
compressionQuality = 0.8, reduced the memory spike by 3MB on average over 100 writes.
However, it does reduce the size of the data on disk (obviously)but this does not help me.
UIImagePNGRepresentation in place of UIImageJPEGRepresentation is worse for this. It is slower and results in higher spikes.
Is it possible that this approach with ImageIO would be more efficient? If so why?
If anyone has any suggestions it would be great. Thanks
Edit:
Notes on some of the points outlined in the questions below.
a) Although I was saving multiple images, I was not saving them in a loop. I did a bit of reading around and testing and found that an autorelease pool wouldn't help me.
b) The photos were not 60Mb in size each. They were photos taken on the iPhone 4S.
With this in mind I went back to trying to overcome what I thought the problem was; the line NSData *data = UIImageJPEGRepresentation(image, 1.0);.
The memory spikes that were causing the crash can be seen in the screenshot below. They corresponded to when UIImageJPEGRepresentation was called. I also ran Time Profiler and System Usage which pointed me in the same direction.
Long story short, I moved over to AVFoundation and took the photo image data using
photoData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
Which returns an object of type NSData, I then used this as the data to write using NSFileManager.
This removes the spikes in memory completely.
i.e
[self saveImageWithData:photoData];
where
+ (void)saveImageWithData:(NSData *)imageData withName:(NSString *)name {
NSData *data = imageData;
DLog(#"*** SIZE *** : Saving file of size %lu", (unsigned long)[data length]);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:name];
[fileManager createFileAtPath:fullPath contents:data attributes:nil];
}
PS: I have not put this as an answer to the question incase people feel it does not answer the Title "Most memory efficient way to save a photo to disk on iPhone?". However, if the consensus is that it should be I can update it.
Thanks.
Using UIImageJPEGRepresentation requires that you have the original and final image in memory at the same time. It may also cache the fully rendered image for a while, which would use a lot of memory.
You could try using a CGImageDestination. I do not know how memory efficient it is, but it has the potential to stream the image directly to disk.
+(void) writeImage:(UIImage *)inImage toURL:(NSURL *)inURL withQuality:(double)inQuality {
CGImageDestinationRef destination = CGImageDestinationCreateWithURL( (CFURLRef)inURL , kUTTypeJPEG , 1 , NULL );
CFDictionaryRef properties = (CFDictionaryRef)[NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:inQuality] forKey:kCGImageDestinationLossyCompressionQuality];
CGImageDestinationAddImage( destination , [inImage CGImage] , properties );
CGImageDestinationFinalize( destination );
CFRelease( destination );
}
Are your images actually 60MB compressed, each? If they are, there's not a lot you can do if you want to save them as a single JPEG file. You can try rendering them down to smaller images, or tile them and save them to separate files.
I don't expect your ImageIO code snippet to improve anything. If there were a two-line fix, then UIImageJPEGRepresentation would be using it internally.
But I'm betting that you don't get 60MB from a single image. I'm betting you get 60MB from multiple images saved in a loop. And if that's the case, then there is likely something you can do. Put an #autoreleasepool{} inside your loop. It is quite possible that you're accumulating autoreleased objects, and that's leading to the spike. Adding a pool inside your loop allows it to drain.
Try to use NSAutoReleasePool and drain the pool once u finish writing the data.
I am having some troubles saving images path into Documents folder and store the path into my sqlite3 local db.
Today I successfully stored the images as BLOB into the db, but reading around the Internet, people said that this is not recommended.
So, now I'm trying to store image path in DB but..
Situation
User taps a button to choose an image (via UIImagePickerView) or take a new photo
After choosing the image, an imageView is set with the image chosen.
Code for picker:
-(void)imagePickerController:(UIImagePickerController *)pickr didFinishPickingMediaWithInfo:(NSDictionary *)info{
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
/* Test code for saving data */
//NSString *dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//NSString *pngPath = [NSString stringWithFormat:#"%#/test.png",dir];
//NSData *data = [NSData dataWithData:UIImagePNGRepresentation(image)];
//[data writeToFile:pngPath atomically:YES];
[imageView setImage:image];
picture = YES;
[pickr dismissModalViewControllerAnimated:YES];
}
Now I got a couple of questions
Into my db, image is BLOB type. Should I edit it to TEXT?
How is the path saved? I mean, in the test code /test.png is a generic image name. Does the chosen image name get saved too so that I can retrieve it? Or, better, if I save an image picked from my library with name "IMG0001", does it get saved as "test"?
What's the right way to save an image into documents folder, its path to the DB and then retrieve it?
I googled a lot to find an answer, but after experimenting a lot I gave up.
Thanks in advance
Here is nice tutorial on how to save images in Documents directory
You need to convert your BLOB field to TEXT field and just save file name of that image. You can also create folder in Documents directory and then access by foldername/filename.png.
Hope this information helps you..
EDIT
Here is code to check if that folder exits, If not exist create new that folder
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentsDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"MyFolder"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath]){
NSError* error;
if( [[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error])
;// success
else
{
NSLog(#"[%#] ERROR: attempting to write create MyFolder directory", [self class]);
NSAssert( FALSE, #"Failed to create directory maybe out of disk space?");
}
}
You cannot directly access the image from the user's photo library, since you have access only to your Applications Sandbox folder.
So if you don't want to store the image as a blob file in the database, you should save the image in the caches folder inside your App's documents folder and then store the filename you set to the image in the database.
Since all images are going to be in the same path folder, you don't need to actually save the entire path in the database, but only the filename should be sufficient enough.