iOS - Saving multiple images to Documents folder - ios

I have this code this code to save an image to the Documents folder.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDirectory stringByAppendingPathComponent:#"savedImage.png"];
UIImage *image = imageView.image; // imageView is my image from camera
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:NO];
I am looking for a way to be able to save multiple images as this one keeps over writing the savedImage.png name.
I do not mind looking for it on google or whatever, but I need to know what it is called, since looking with the wrong keywords really delays the world:-)
Cheers

You need to change the file name that you are appending to the image documentsDirectory path on line three. Each time you'll need to use a different name that isn't already used. NSFileManager has methods to see if a file exists so you can construct a file name and then test if it exists in that location and if so, increment your duplicate count and try the next one.
if num is an integer you define somewhere and keep around so you know the last one you thought you used (and that you've initialized to 1 somewhere).
// your code to get the directory here, as above
NSFileManager *fm = [NSFileManager ...]
do {
savedImagePath = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithFormat: #"%#-%d.png", #"savedImage", num]];
num += 1; // for next time
if ( ![fm fileExistsAtPath: savedImagePath] )
{
// save your image here using savedImagePath
exit;
}
} while ( //some kind of test for maximum tries/time or whatever )
you'll have to look up the syntax to get an NSFileManager instance and the exact file exists method signature, but that's the gist of it.

If you save file with current dateTime you don't need to worry about same name override problem
-(NSString*)getCurrentDateTimeAsNSString
{
NSDateFormatter *format = [[NSDateFormatter alloc] init];
[format setDateFormat:#"yyyyMMddHHmmss"];
NSDate *now = [NSDate date];
NSString *retStr = [format stringFromDate:now];
[format release];
return retStr;
}

you can create a new file each time to do that.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"YOURiMAGEfILE.IMAGEeXTENSION"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path])
{
path = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat: #"YOURiMAGEfILE.IMAGEeXTENSION] ];
}
and then you can perform your above operations on the created file.

Hope it help You Working For me!!
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage
{
let i = Int(arc4random())
let str = String(i).appending(".png")
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(str)
print(paths)
let imageData = UIImagePNGRepresentation(image)
fileManager.createFile(atPath: paths as String, contents: imageData, attributes: nil)
}
else{
print("error")
}

Related

Saved photos don't appear on device after update

On my iPhone app, I'm saving pictures associated with an event via this code:
[pngData writeToFile:filePath atomically:YES]; //Write the file
self.thisTransaction.picPath = filePath;
Later I retrieve and display the photo with this code:
UIImage * image = [UIImage imageWithContentsOfFile:thisTransaction.picPath];
Works great on my iPad (I don't have an iPhone).
However, if I update the app by connecting the iPad to my MB Pro after an Xcode code modification not involving the above lines, then disconnect and run it independently, the picture at the expected picPath is not retrieved. All other data associated with thisTransaction in Core Data is intact and unchanged, but the expected picture doesn't appear on the device after the update.
Can someone please tell me where I'm going wrong?
Edit to clarify file path construction
pngData = UIImagePNGRepresentation(capturedImage.scaledImage);
NSLog(#"1 The size of pngData should be %lu",(unsigned long)pngData.length);
//Save the image someplace, and add the path to this transaction's picPath attribute
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
int timestamp = [[NSDate date] timeIntervalSince1970];
NSString *timeTag = [NSString stringWithFormat:#"%d",timestamp];
filePath = [documentsPath stringByAppendingPathComponent:timeTag]; //Add the file name
NSLog(#"1 The picture was saved at %#",filePath);
The console log shows this filePath:
/Users/YoursTruly/Library/Developer/CoreSimulator/Devices/65FB33E1-03A7-430D-894D-0C1893E03120/data/Containers/Data/Application/EB9B9523-003E-4613-8C34-4E91B3357F5A/Documents/1433624434
The issue you are having is that the location of an app's sandbox can change over time. Typically this happens when an app is updated. So the worst thing you can do is persist absolute file paths.
What you need to do is persist just the part of the path relative to the base path (the "Documents" folder in this case).
Then when you want to reload the file again, append the persisted relative path to the current value of the "Documents" folder.
So your code needs to be something like this:
Save the file:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
int timestamp = [[NSDate date] timeIntervalSince1970];
NSString *timeTag = [NSString stringWithFormat:#"%d",timestamp];
filePath = [documentsPath stringByAppendingPathComponent:timeTag]; //Add the file name
[pngData writeToFile:filePath atomically:YES]; //Write the file
self.thisTransaction.picPath = timeTag; // not filePath
Load the file:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
NSString *filePath = [documentsPath stringByAppendingPathComponent:thisTransaction.picPath];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];

Reading and Writing NSMutableArray to iPhone not working (Works on iOS Simulator)

In an app I am working on I want to have an NSMutableArray (called pathsArray) that I can read from a file in the app's directory, be able create an instance of that array that I can add objects to and/or remove objects from, and then I want to write it back to the file. I have a UILabel that shows the number of contents in this array. My problem: my code below works fine on Xcode's iOS Simulator but when I try to run the app on my actual iPhone the data isn't saved. I know there are a lot of questions on here related to this issue but i can't seem to see what I am doing wrong. Any help would be greatly appreciated.
- (void) loadArrayContents {
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
NSString* filePath = [documentsDirectory stringByAppendingString:#"theArray"];
//Objects contained in an array returned by 'initWithContentsOfFile' are immutable even if the array is mutable
NSArray* contentsArray = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
pathsArray = [[NSMutableArray alloc] initWithArray:contentsArray];
}
and...
- (void) saveArrayContents {
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
NSString* filePath = [documentsDirectory stringByAppendingString:#"theArray"];
[pathsArray writeToFile:filePath atomically:YES]);
}
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"theArray"];
should solve the issue. The problem with
NSString *filePath = [documentsDirectory stringByAppendingString:#"theArray"];
is that it does not add / in the file path.

Proper way to giving name to file when [NSData writeToFile:] used

I'm using that code to save downloaded videos from internet to application document folder:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *save_it = [documentsDirectory stringByAppendingPathComponent:video_filename];
NSError *error;
BOOL success = [fileData writeToFile:save_it options:0 error:&error];
if (!success) {
NSLog(#"writeToFile failed with error %#", error);
}
it works, but if there is a slash "/" in the video_filename it breaks because of slash is directory seperator, I know.
For example when video_filename is : Best Video / Best Song Ever.3gpp , log says:
{NSFilePath=/Users/Apple/Library/Developer/CoreSimulator/Devices/5A7D36F5-6EDB-495D-9E8E-B9EB22E5357C/data/Containers/Data/Application/B1D0AC48-D84C-4A0D-9F09-08BF4C45DD32/Documents/Best Video / Best Song Ever.3gpp, NSUnderlyingError=0x7d339430 "The operation couldn’t be completed. No such file or directory"}
I don't know is there any other special character that will make crashing,
So what is the best way of cleaning these special characters from nsstring ?
We can make SEO friendly urls in PHP, I'm searching a function like that to do this.
The first problem I see here is that your file path includes some spaces. in the example you gave, the value of video_filename variable is "Best Video / Best Song Ever.3gpp" which includes spaces around the slash. You first have to delete the spaces, this might help you do that:
NSArray *components = [video_filename componentsSeparatedByString:#"/"];
for (NSInteger i = 0, i < components.count, ++i) {
NSString *string = components[i];
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
components[i] = string;
}
NSString *path = [components componentsJoinedByString:#"/"];
If I understood correctly, your video_filename might be either in the form xxx.3gpp or yyy/xxx.3gpp. If it's the format of yyyy/xxxx.3gpp, you first have to create a directory named yyyy and then save the file to that directory.
This might help you do that:
- (void)createDirectory:(NSString *)directoryName
atFilePath:(NSString *)filePath
{
NSString *filePathAndDir = [filePath
stringByAppendingPathComponent:directoryName];
NSError *error;
if (![[NSFileManager defaultManager] createDirectoryAtPath:filePathAndDir
withIntermediateDirectories:NO
attributes:nil
error:&error]) {
NSLog(#"Create directory error: %#", error);
}
}
and the way you would use this is
[self createDirectory:components[0] atFilePath:documentsDirectory];
hope this helps!
So if your filename is actually "Best Video / Best Song Ever.3gpp" I am sorry but nothing easy comes to mind.
Now if Best Video is a folder where you will save your file you can use :
+(NSString*) getPathToFolder:(NSString*) folderName {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *folderPath = [documentsPath stringByAppendingPathComponent:folderName];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:folderPath]) {
NSLog(#"Creating a new folder at\n%#", folderPath) ;
[fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:NO attributes:nil error:nil];
}
return folderPath ;
}
This will check if your folder exist or not, if it does not exists then it will create it.
it will return the path you will want to use to save your file.
Now regarding the naming of the files, using spaces is highly unadvisable, I suggest using :
NSString* pathWITHSpaces ;
NSString* pathWithoutSpaces = [pathWITHSpaces stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
Hope this helps a bit

How can I get the file path from a UIImage?

Usually it's the other way around, you use the path to display the image. I was wondering if you can get the path if you already have the image.
if you already have the image i.e. have added the file to your resources, you can use this to get the file path;
NSString *string = [[NSBundle mainBundle] pathForResource:#"IMAGE_FILE_NAME" ofType:#"jpg"]; // or ofType:#"png", etc.
I don't believe it is possible to get it directly from UIImage.
Best way is to save the image in a directory, then you will have the file path.
//image is your UIImage;
if (image != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
#"test.png" ];
NSData* data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:YES];
}
path will be your filepath.
Once a UIImage is created, the image data is loaded into memory and no longer connected to the file on disk. As such, the file can be deleted or modified without consequence to the UIImage and there is no way of getting the source path from a UIImage.

Storing and reading files from Documents directory iOS 5

In my game, when a level is completed the app stores a "1" in a file in the Documents directory of the app. When the game then loads, a player can only play a level if the previous level has been completed. When I test the game via Xcode and on a device the app works properly and a level cannot be played until the previous level has been completed. However, when the app was approved and released on the App Store, the app behaves as if each level has been completed (no locked levels). I can't figure this one out and would appreciate someone's help! The devices I'm testing on are all iOs 5.0 or higher.
Below is the code that saves the completed level in the Documents directory:
NSMutableData* data = [[NSMutableData alloc] init];
NSKeyedArchiver* coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSString *levelString = [NSString stringWithFormat:#"Level%d",level];
[coder encodeInteger:1 forKey:levelString];
[coder finishEncoding];
NSString *levelString2 = [NSString stringWithFormat:#"Level%d.save",level];
///
NSFileManager *filemgr;
NSString *dataFile;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Identify the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
dataFile = [docsDir stringByAppendingPathComponent:levelString2];
// Check if the file already exists
if ([filemgr fileExistsAtPath: dataFile])
{
[[NSFileManager defaultManager] removeItemAtPath:dataFile error:nil];
}
[data writeToFile:dataFile atomically:YES];
[coder release];
[data release];
}
#catch (NSException* ex)
{
CCLOG(#"level save failed: %#", ex);
}
Below is the code that reads the Document directory to see if the level has been completed:
if ([self loadCompletedLevels:6] == 1) { //// level gets unlocked **** }
-(int) loadCompletedLevels:(int)theLevel; {
int isLevelCompleted; //1 = completed
NSString* kSaveFile = [NSString stringWithFormat:#"Level%d.save",theLevel];
NSString *levelString = [NSString stringWithFormat:#"Level%d",theLevel];
#try
{
NSFileManager *filemgr;
NSString *dataFile;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Identify the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
dataFile = [docsDir stringByAppendingPathComponent:kSaveFile];
if ([[NSFileManager defaultManager] fileExistsAtPath:dataFile])
{
NSData* data = [[NSData alloc] initWithContentsOfFile:dataFile];
if (data && [data length] > 0)
{
NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
isLevelCompleted = [decoder decodeIntForKey:levelString];
[decoder release];
}
[data release];
}
if (isLevelCompleted == 1) {
levelCompleted = YES;
}
}
#catch (NSException* ex)
{
levelCompleted = NO;
}
return isLevelCompleted; }
You should probably use a different approach for storing your data, but the real problem is that you are not initializing the return value, isLevelCompleted. It is on the stack, and does not have a default value. It starts out with whatever happens to be at that stack location.
So, if you don't set it, it will have an arbitrary value.
Also, you should probably use BOOL for a boolean value, but if you make this:
int isLevelCompleted = 0; //1 = completed
you will initialize it to "false" so it must be explicitly changed to "true" by your code.

Resources