Sunday, July 3, 2011

[iOS5] Saved Files Alert!

Game, utility or whatever app you are developing. Know this... as of iOS 5; the USER will have full visibility into your apps Document folder. <- as of beta 2

I have already started saving my apps data to the /Library folder but in my recent game WARP gce, the "save" files are still in /Documents.

So on your iOS 5 test device go to Settings -> General -> Usage and select an app
We're are still under NDA so these images may go away.
Here's a couple of screenshots

Now let's swipe to delete - YIKES!

Now even in Hyper WARP I have a method to check that the file "is there" before it tries to load it but if you don't, well only YOU know what will happen ;-)

Oh and in the new multi-tasking order of things... you guessed it... you can of course DELETE these files WHILE your app is in the background, which again; only YOU know what problems that will cause.

What can we do / what should we do?
I think giving the user control over the device is certainly the right move and we as developers have always been able to choose which folder to save our files to. From all that I have seen and read the "/Library" folder is "our" folder and the normal user, even with iOS 5 will never "see" in there. So that is where all my "app" files are stored and starting with version 2.0 of WARP gce; so will the saved game files.

Finally time for another code snippet!

Here is my "move" method I'm using in WARP gce
playerDefaults is a pointer to [NSUserDefaults standardDefaults]
kDidMoveSavedFiles is a #define macro

-(void) moveSavedFilesToLibrary 
    // only run once kDidMoveSavedFiles
    if ([playerDefaults integerForKey:kDidMoveSavedFiles] < 1) {
        // documents folder
        NSArray *docFilePath = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES); 
        NSString *documentsDirectory = [docFilePath objectAtIndex: 0];
        // library folder
        NSArray *libFilePath = NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES); 
        NSString *libraryDirectory = [libFilePath objectAtIndex: 0];
        // file manager
        NSError *error;
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSDirectoryEnumerator *direnum = [fileManager enumeratorAtPath:documentsDirectory];
        // while variables
        NSString *pname;
        int fileNum = 1;         // my saved game files start with, next would be
        int scoreNum = 0;        // my saved high-score files start with 0.scores for type 0 games, 1.scores for type 1 games, etc
        while ((pname = [direnum nextObject])) {
            // remove any .txt file <- you perhaps don't want to do this, I did :-)
            if ([[pname pathExtension] isEqualToString:@"txt"]) {
                [fileManager removeItemAtPath:[NSString stringWithFormat:@"%@/%@",documentsDirectory,pname] error:&error];
            if ([[pname pathExtension] isEqualToString:@"scores"]) {
                NSString *orgFileName = [NSString stringWithFormat:@"%@/%i.scores", documentsDirectory, scoreNum];
                NSString *newFileName = [NSString stringWithFormat:@"%@/%i.scores", libraryDirectory, scoreNum];
                // move file from Documents to Library
                [fileManager moveItemAtPath:orgFileName toPath:newFileName error:&error];
            if ([[pname pathExtension] isEqualToString:@"save"]) {
                NSString *orgFileName = [NSString stringWithFormat:@"%@/", documentsDirectory, fileNum];
                NSString *newFileName = [NSString stringWithFormat:@"%@/", libraryDirectory, fileNum];
                // move file from Documents to Library
                [fileManager moveItemAtPath:orgFileName toPath:newFileName error:&error];
                // check for matching log file
                NSString *orgLogName = [NSString stringWithFormat:@"%@/log%i.log", documentsDirectory, fileNum];
                if ([fileManager fileExistsAtPath:orgLogName]) {
                    NSString *newLogName = [NSString stringWithFormat:@"%@/log%i.log", libraryDirectory, fileNum];
                    [fileManager moveItemAtPath:orgLogName toPath:newLogName error:&error];
        // now update NSUserDefaults so this will never run again!
        [playerDefaults setInteger:2 forKey:kDidMoveSavedFiles];

Update: I'm working on adding analytics and guess what shows up...

Which of course is NOT a problem because those of you that are already using Google Analytics of course complied with the terms and notified your users... right?


  1. Is it just one save game slot or can you have 3 like your screenshot suggests? If it can have 3 then maybe you should leave them in the documents directory as it would be quite useful for a user to delete un-used game slots when they near their 5GB.

  2. Hi Richard, yeah my game allows an unlimited number of saved games. However when the game ends, that saved game is deleted. The user can also delete saved games from within the app, but from the main menu, not while a game is loaded. <- that's what will cause a problem, deleting a game file while it is still running (in the background).

    I have other, older games that will still use the /Documents folder and it will be a "lesson learned" for the user if they delete a saved game from Settings ;-)

    Just thought it was worth mentioning if your app / game was in development.

  3. This comment has been removed by the author.

  4. what about rename files postponing a period?

    I mean from "myImage.png" to ".myImage.png", since as you know, Unix based systems hides "periodded" files.

    Thanks for the article!

  5. @GiovaMaster - interesting and most likely that would work. If I have a chance to test that I will update the post.

  6. Can this formula be used separate my saved game files from the other files? Cause I want my saved games to be isolated, so it won't get affected by those slow loading times. I think changing the file name wouldn't help it load faster in my situation.

    Ruby Badcoe

  7. @Ruby - the location of your files should not effect performance at all.