6

My app is using the NSDocumentDirectory to save images in it, I just wanna ask if its the safe way to save images(100 maximum). I have read several thread & questions with answers about it, though I dont know which to follow.Some say that its okay to save there. Some say I shouldnt use NSDocumentDirectory for saving, because it will be back-up by the iCloud. So where can I save it that when the user exit the app then run the app again, then images should still be there?. I dont know much about the tmp directory or cache directory. But if its either one of the 2 that I should use, How can I use them in my code here:

                NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory,    NSUserDomainMask ,YES );
                NSString *documentsDir = [paths objectAtIndex:0];
                NSString *savedImagePath = [documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"Images%d.png", i]];
                ALAssetRepresentation *rep = [[info objectAtIndex: i] defaultRepresentation];
                UIImage *image = [UIImage imageWithCGImage:[rep fullResolutionImage]];
                //----resize the images
                image = [self imageByScalingAndCroppingForSize:image toSize:CGSizeMake(256,256*image.size.height/image.size.width)];

                NSData *imageData = UIImagePNGRepresentation(image);
                [imageData writeToFile:savedImagePath atomically:YES];

Thank you so much for the help.

Bazinga
  • 2,456
  • 33
  • 76
  • Do you want these images to be backed up? Are the large? Will the user like you using their precious iCloud storage space? – sudo rm -rf Jul 25 '12 at 01:18
  • @sudorm-rf, yes i need them to be backed up for my game. slot machine, which uses the user picked pictures form their own photo library. – Bazinga Jul 25 '12 at 01:53

4 Answers4

19

The tmp and cache directories are periodically cleaned up by iOS. If the images are for general use, use the camera roll as the other two answers suggest. However if these images are intended just for the scope of your app, you can still safely store them in the Documents directory, you just have to include an "exclude from iCloud backup" function call to each file after saving, in order to prevent Apple rejecting your app for using too much iCloud space. Of course there's a trade-off, disabling this means the user will lose their photos anyway should they delete the app or get another device(etc), but this caveat is preferable to not getting the App on the store at all.

To disable iCloud backup on a file, there's two methods for iOS versions > 5.0:

UPDATE! MERGED BOTH METHODS INTO A SINGLE FUNCTION THAT AUTOMATICALLY HANDLES iOS VERSION:

#include <sys/xattr.h> // Needed import for setting file attributes

+(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)fileURL {

    // First ensure the file actually exists
    if (![[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
        NSLog(@"File %@ doesn't exist!",[fileURL path]);
        return NO;
    }

    // Determine the iOS version to choose correct skipBackup method
    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];

    if ([currSysVer isEqualToString:@"5.0.1"]) {
        const char* filePath = [[fileURL path] fileSystemRepresentation];
        const char* attrName = "com.apple.MobileBackup";
        u_int8_t attrValue = 1;
        int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
        NSLog(@"Excluded '%@' from backup",fileURL);
        return result == 0;
    }
    else if (&NSURLIsExcludedFromBackupKey) {
        NSError *error = nil;
        BOOL result = [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
        if (result == NO) {
            NSLog(@"Error excluding '%@' from backup. Error: %@",fileURL, error);
            return NO;
        }
        else { // Succeeded
            NSLog(@"Excluded '%@' from backup",fileURL);
            return YES;
        }
    } else {
        // iOS version is below 5.0, no need to do anything
        return YES;
    }
}

If your app must support 5.0, then unfortunately your only option is to save those photos in the Caches directory, which means they won't be backed up (this not causing an App Store rejection for that reason), but whenever the storage watchdog decides it's time to clean the Caches folder, you'll lose those photos. Not an ideal implementation at all, but such is the nature of the beast in 5.0, where Apple added in Backup exclusion as an afterthought.

EDIT: Forgot to answer the 'how to save to the tmp/cache directory' part of the question. If you do decide to go down that path:

  • Saving to tmp:

    NSString *tempDir = NSTemporaryDirectory();
    NSString *savedImagePath = [tempDir stringByAppendingPathComponent:[NSString stringWithFormat:@"Images%d.png", i]];
    

(note that this won't appear to have any effect in the simulator, but it works as expected on device)

  • Saving to Cache:

    NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES)lastObject];
    NSString *savedImagePath = [cacheDir stringByAppendingPathComponent:[NSString stringWithFormat:@"Images%d.png", i]];
    
andycam
  • 1,682
  • 14
  • 27
  • Ultimately that's a design choice you have to decide! We've given you the options for storage, you have to pick the one that best implements how you want your app to function. Personally I'd go with saving to Documents and excluding from iCloud backup, it's the most common choice for a reason, even though you don't get iCloud backup for those files, the user isn't going to lose anything off their device for (seemingly) no reason, which would make them very angry! – andycam Jul 25 '12 at 02:15
  • Im thinking of the excluding thing. i just have to put it on code, could you give me guidance I'm new here. After i copy it in the class. how am i going to activate it on code? – Bazinga Jul 25 '12 at 05:07
  • 1
    Okay I updated my answer, it's now a single method that auto-detects and handles iOS version, even easier! All you have to do is create an NSURL of the filePath (so for example, the code you gave, you'd put at the end: `NSURL *fileURL = [NSURL URLWithString:savedImagePath];`, and then you just pass that into the exclude function like this: `[self addSkipBackupAttributeToItemAtURL:fileURL];`! That should work perfectly :) – andycam Jul 25 '12 at 05:59
  • I'm using this in version 5.0 and onwards, how am i going to do it, kinda confuse though. cause it says "NSURLIsExcludedFromBackupKey undeclared". and how to use it on other versions? – Bazinga Aug 06 '12 at 02:55
  • I specified > 5.0 because Apple didn't include the backup exclusion code in 5.0. Unfortunately you don't have any way to do backup exclusion for users still on 5.0. – andycam Aug 06 '12 at 03:01
  • what if my version starts on 5.0? – Bazinga Aug 06 '12 at 05:36
  • 1
    From this [Apple doc:](http://developer.apple.com/library/ios/#qa/qa1719/_index.html) "It is not possible to exclude data from backups on iOS 5.0. If your app must support iOS 5.0, then you will need to store your app data in Caches to avoid that data being backed up. iOS will delete your files from the Caches directory when necessary, so your app will need to degrade gracefully if it's data files are deleted." – andycam Aug 06 '12 at 05:40
  • oh okay then. so my question is when is caches directory deleted? every what time? – Bazinga Aug 06 '12 at 05:41
  • It's not any pre-determined time, the system itself decides whenever the storage is getting full and needs a clean-out, it deletes the Cache folder in the user's apps. You can't really plan for it, you just have to ensure your app degrades gracefully if you must support 5.0. (Ps. Stack Overflow discourages lengthy discussions in comments, for readability purposes. You may want to search for more specific questions about iOS 5.0 and Cache deleting, and please consider accepting an answer since your initial question has been well and truly answered here.) – andycam Aug 06 '12 at 05:48
  • What can I do to support only 5.0.1 or greater? On xcode I can change the deployment target to 5.0 or 5.1, but not 5.0.1 – jcesarmobile Sep 04 '12 at 11:32
  • 1
    @jcesar It seems there's no way of doing this in xCode unfortunately ([Refer here](http://stackoverflow.com/a/11318483/465893)), but as that post suggests, best to handle all 3 versions as best you can (degrade gracefully when feature is missing). It's annoying, but the best we can do until the user-base eventually all migrate from 5.0 (should accelerate further with the release of iOS 6 this month!) Hope that helps. – andycam Sep 04 '12 at 11:50
  • which version of + (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL is better? yours or the one on the link? And in the link he says we should use NSApplicationSupportDirectory, I have never heard of this, right now I use NSDocumentDirectory, should I change to NSApplicationSupportDirectory? – jcesarmobile Sep 04 '12 at 12:49
  • Either version does the same job; his is more compact, mine offers more error handling and feedback. Whichever suits your needs better! NSApplicationSupportDirectory is essentially the new implementation of Caches... for 5.0 and below use Caches, for 5.0.1+ it's better to use NSApplicationSupportDirectory. [Docs on it here.](http://developer.apple.com/library/ios/DOCUMENTATION/FileManagement/Conceptual/FileSystemProgrammingGUide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW28) – andycam Sep 04 '12 at 13:03
  • And NSApplicationSupportDirectory isn't deleted? BTW, your code is wrong, it never enters in the "if ([currSysVer floatValue] >= 5.1)" with my ios 5.1 simulator, and you should tell we have to include #include to use the setxattr function. – jcesarmobile Sep 04 '12 at 15:11
  • NSApplicationSupportDirectory is similar to Caches, it's used for storing files that can be re-generated by the app if need be, but they can be deleted by the system periodically. As for the code sample, you're right, seems it's an issue with comparing floats. I changed the 5.1 condition to check for the existence of `NSURLIsExcludedFromBackupKey` instead of iOS version, which I've since learned is better practice anyway, and it should now work. Added the import as well. Thanks for pointing those out! – andycam Sep 04 '12 at 23:36
  • Just to be clear, so if I don't want my files to be deleted by the system, in iOS >= 5.0.1 I have to store them into NSDocumentDirectory and use your +(BOOL)addSkipBackupAttributeToItemAtURL function to avoid the backup into icloud. And in iOS < 5.0.1 I should store them in NSCachesDirectory. Right? – jcesarmobile Sep 05 '12 at 07:57
  • First sentence is correct! Second sentence is correct only if those files are able to be re-generated/re-downloaded by the app, as the Caches folder can be cleaned out by the system periodically (it's never backed up to iCloud though, so no need to use the function on those). – andycam Sep 05 '12 at 22:44
2

If you want the user to be able to use the images in other apps or view them along with their photos, use the photo album as Mike D suggest. If the files are something you generate locally for use with your app only, then you should probably use the documents directory. You can expose the documents directory to iTunes with the info.plist option "Application supports iTunes file sharing" which will allow the user to add or delete files through iTunes, but the files will not be exposed to other apps

1

You are saving scaled images so they are really only useful for your game. They are not going to be very large and will not take up much space. You could save them in the Library directory for the app and avoid the whole iCloud thing, as it doesn't sound like there is any reason to back them up. Also, saving the the Library avoid the possibility of the user deleting them, if for some other reason you have iTunes sharing turned on.

Update: code for saving to the app Library directory

- (void)saveSequences:(NSMutableDictionary*)sequences
{
    NSArray *path = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSString *libDirectory = [path objectAtIndex:0];
    NSString *settingsPath = [libDirectory stringByAppendingPathComponent:@"userSequences.plist"];

    NSLog(@"settingsPath %@", settingsPath);

    [sequences writeToFile:settingsPath atomically:YES];
}

// The code below gets the path to a named directory in the 'Documents' folder - and if it doesn't exist, creates it. Adjust it to use the Library path, if you decide to go that route.

- (NSString *)getDirectoryBySequenceName:(NSString *)sequenceName 
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString * documentDirectory = [paths objectAtIndex:0];
    NSString * sequenceDirectory = [documentDirectory stringByAppendingPathComponent:sequenceName];

    NSError *error;
    BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:sequenceDirectory 
                                             withIntermediateDirectories:YES 
                                                              attributes:nil error:&error];   

    if (!success) {
        NSLog(@"Error creating data path: %@", [error localizedDescription]);
    }

    return sequenceDirectory;
}
spring
  • 18,009
  • 15
  • 80
  • 160
  • how can I apply the library directory in my code? does it behave the same way as `NSDocumentDirectory`? – Bazinga Jul 25 '12 at 03:05
  • I added some code for that in my answer. If you go that route, I'd suggest creating a directory to store your images in so that you can get at them easily using `contentsOfDirectoryAtPath` – spring Jul 25 '12 at 03:25
  • I'm going to save them in plist, I'm kinda new to this method sorry. – Bazinga Jul 25 '12 at 03:27
  • I've never saved images in a plist. Would be concerned about memory - having to read the whole list in to grab just one or a few images. Might not be an issue - maybe research it a bit more to be sure. – spring Jul 25 '12 at 03:29
  • Oh - yeah. I just copied out some code from a project. You will have to change it to save images. You already have that in the code you posted. – spring Jul 25 '12 at 03:39
  • Rejected for writing to the Library directory? I don't believe so. Check out http://stackoverflow.com/questions/2717545/how-to-access-the-iphone-library-folder-programmatically. – spring Jul 25 '12 at 03:49
  • Okay, so what I'm going to do is save in Library Directory, then make a new Directory, could that be good? I don't quite get the code you posted. – Bazinga Jul 25 '12 at 03:52
  • Play with it on the simulator - you can see that the folder gets created and the images saved, etc. – spring Jul 25 '12 at 03:59
  • /Library/ is still included in iCloud backup by default, thus you still need to remove those files from iCloud backup as I outline in my answer. The folder that's excluded from backup is /Library/Caches. [Source.](http://developer.apple.com/library/ios/#qa/qa1699/_index.html) Also you don't need to do any of this unless you added the key UIFileSharingEnabled to Info.plist – andycam Jul 25 '12 at 04:12
0

Depending on the purpose of your app, you could save it to the photos app (UIImageWriteToSavedPhotosAlbum(image, self, nil, nil) I think, Apple reference). Saving here, or in the documents directory (or any sub folder), will allow the user backup those images to iCloud or iTunes, if the user chooses too and/or if they have set up iCloud.

Since you state the images need to persist between launches, the temp or cache directory get emptied when the application is removed from memory, maybe sooner (the O/S decides).

More about the iOS file system.

Mike D
  • 4,938
  • 6
  • 43
  • 99
  • cause i need to save the images from the camera roll, so I cant save it there too. okay? – Bazinga Jul 25 '12 at 01:11
  • @Kobe.o4 I don't really understand the question. Do mean you need to save images taken with the camera in an application folder? – Mike D Jul 25 '12 at 15:01