Path / Location of Cocos2d-x UserDefault data in IOS? - unity3d

I am tasked to migrate an IOS game made with Cocos2d-x into Unity. One of the issues I have is that I don't know where Cocos2d-x writes the user's saved data. The Unity version of the app needs to access that data so that the user doesn't lose their progress.
The Cocos2d-x application saves it's data using something like this: userDefault->setIntegerForKey("coins", 35);
Would anybody know what path/location that user's saved data is stored? Are there ways I can find that out? I've already tried to view it on xcode via Window > Devices and Simulators > Installed Apps but the app isn't listed.
Any help would be appreciated. Thanks.

In cocos2d-x v4:
// write string
[[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithUTF8String:value.c_str()] forKey:[NSString stringWithUTF8String:pKey]];
// read string
NSString *str = [[NSUserDefaults standardUserDefaults] stringForKey:[NSString stringWithUTF8String:pKey]];
// read integer
NSNumber *value = [[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithUTF8String:pKey]];
Before V 2.1.2 the info was stored in UserDefault.xml
#define XML_FILE_NAME "UserDefault.xml"
#ifdef KEEP_COMPATABILITY
if (! _isFilePathInitialized)
{
// xml file is stored in cache directory before 2.1.2
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
_filePath = [documentsDirectory UTF8String];
_filePath.append("/");
_filePath += XML_FILE_NAME;
_isFilePathInitialized = true;
}
#endif

Related

is saving in NSDocumentDirectory okay?

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.
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]];
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
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;
}
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.

iPhone: Storing audio data on NSUserDefaults and memory issues

I am downloading several small size songs from server and storing(NSData) into NSUserDefaults, so that i can use it when needed to cache and play directly on the device, instead of downloading and playing again from server.
The problem, if i store few couple of smaller size songs as an data format in NSUserDefaults, it reduces lots of device memory and throwing memory warning or crashing etc.
Could you someone guide me how can i resolve it? How can i store song data persistently on the device for Cache purpose, and same time storing in less memory usage?
UPDATED: As per the suggestion, i tried to add song data into dictionary as file and tried to retrieve it as below. But, still i'm facing the same issue, memory warning after around 30mb of data retrieved.. Could someone help me to resolve this? I need to store around 40 mb of song data and store it.
NSURL *songurl = [NSURL URLWithString:downloadSOngUrl];
NSMutableData *songdata = [NSMutableData dataWithContentsOfURL:songurl];
NSString *fileName = [downloadSOngUrl lastPathComponent];
NSLog(#"fileName: %#",fileName);
[appDelegate writeSongIntoDocsDirectory :songdata :fileName];
-(void) writeSongIntoDocsDirectory :(NSData *) inSongData :(NSString *) songNamePath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSLog(#"songNamePath: %#", songNamePath);
songNamePath = [songNamePath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] ];
NSLog(#"songNamePath: %#", songNamePath);
if ( [inSongData writeToFile:[documentsDirectory stringByAppendingPathComponent:songNamePath] atomically:YES] )
{
// Success !
NSLog(#"Successfully saved the song into documents directory");
}
else
{
// Error !
NSLog(#"Error when Successfully saving song into documents directory");
}
}
-(NSData *) readSongDataFromDocsDirectory :(NSString *) filePath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSData *readData = [NSData dataWithContentsOfFile:[documentsDirectory stringByAppendingPathComponent:filePath]];
return readData;
}
Thanks in advance!
Better store these audio files in documents directory as this will reduce the size of the app , NSUserDefaults is usually used to stored in small settings particular to the app , like the preferences and all for the particular user of the app. hoping this helps. . :)
Whatever you store in the NSUserDefaults will increase the app size as this object is always alive while the app runs...

sqlite database update when app version changes on Appstore in iPhone

I have an app with version 1.0 on app store which uses sqlite database for reading the data.Now I want to update my version to 1.1 with update in database file.While using developer certificate when I install app on device it did not update the database as the database file already exist in documents folder so i have to manually delete the app and install it again.My question is, when any user update the app, will the database also get updated according the current version.Any suggestions are welcome.Thanks
I am sure there are many ways to do this (and many ways better then mine as well), but the way that I handle such problems is as follows:
First I define a constant in the first .h file of the app (the one that will load first) to indicate First Time load and set it to 0:
#define FirstTime 0
Now you have to know that I have the intention to save the value of this constant in the Documents folder for future references, therefore I use a Shared Data Instance. In the viewDidLoad I do the following test:
//if first time run of this version
if( [MyDataModel sharedInstance].count < (FirstTime + 1) )
{
//do what you need to do as the first time load for this version
[MyDataModel sharedInstance].count++
//save the count value to disk so on next run you are not first time
//this means count = 1
}
Now the trick is on your new app version (say 1.1). I change the FirstTime to 2:
#define FirstTime 2
Since the saved First Time value on disc is 1 this means you will be caught by the if statement above, therefore inside it you can do anything you want like delete the old tables and recreate them again with the new formation.
Again not that brilliant, but solves the case!
This approach relies on NSUserDefaults. The idea is to get the previous app version number(if exists) from NSUserDefaults and compare it with the current version.
The code performs db upgrade if the previous app version < than current version or if the previous version is nil. It means that this approach can be used even though the app was already published on the AppStore. It will upgrade database to the new version during the app update.
This is a plist file:
There is an array which is composed of the version number and a set of sql queries for the corresponding upgrade version.
Suppose that a previous version is 1.2 and the actual version is 1.4 the code perform the upgrade only from the version 1.2 to 1.4. If the previous version is 1.3 and the current 1.4 the code performs upgrade only from 1.3 to 1.4.
If the previous version is nil the code performs upgrade to 1.1 then to 1.2 then to 1.3 and finally to 1.4.
NSString * const VERSION_KEY = #"version";
-(void)upgradeDatabaseIfRequired{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *previousVersion=[defaults objectForKey:VERSION_KEY];
NSString *currentVersion=[self versionNumberString];
if (previousVersion==nil || [previousVersion compare: currentVersion options: NSNumericSearch] == NSOrderedAscending) {
// previous < current
//read upgrade sqls from file
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"UpgradeDatabase" ofType:#"plist"];
NSArray *plist = [NSArray arrayWithContentsOfFile:plistPath];
if (previousVersion==nil) {//perform all upgrades
for (NSDictionary *dictionary in plist) {
NSString *version=[dictionary objectForKey:#"version"];
NSLog(#"Upgrading to v. %#", version);
NSArray *sqlQueries=[dictionary objectForKey:#"sql"];
while (![DB executeMultipleSql:sqlQueries]) {
NSLog(#"Failed to upgrade database to v. %#, Retrying...", version);
};
}
}else{
for (NSDictionary *dictionary in plist) {
NSString *version=[dictionary objectForKey:#"version"];
if ([previousVersion compare: version options: NSNumericSearch] == NSOrderedAscending) {
//previous < version
NSLog(#"Upgrading to v. %#", version);
NSArray *sqlQueries=[dictionary objectForKey:#"sql"];
while (![DB executeMultipleSql:sqlQueries]) {
NSLog(#"Failed to upgrade database to v. %#, Retrying...", version);
};
}
}
}
[defaults setObject:currentVersion forKey:VERSION_KEY];
[defaults synchronize];
}
}
- (NSString *)versionNumberString {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *majorVersion = [infoDictionary objectForKey:#"CFBundleShortVersionString"];
return majorVersion;
}
You can use .plist as well:
- (void)isItTheFirstTimeAfterUpdate {
NSString *versionnum;
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"yourplist.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:path]) {
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"yourplist" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath:path error:&error];
}
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
versionnum = #"";
//it can be installed by user (for ex. it is 1.3 but first installed), no plist value is set before
if(([savedStock objectForKey:#"versionnum"]) && (![[savedStock objectForKey:#"versionnum"] isEqualToString:#""])){
versionnum = [savedStock objectForKey:#"versionnum"];
}
//to get the version of installed/updated-current app
NSString *myversion = [NSString stringWithFormat:#"%#",[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"]];
//if no version has been set-first install- or my version is the latest version no need to do sth.
if ([versionnum isEqualToString:myversion] || [versionnum isEqualToString:#""]) {
NSLog(#"Nothing has to be done");
}
else {
[self cleanDB];//i have clean tables and create my new db tables maybe logout the user etc.
[savedStock setObject:[NSString stringWithString:myversion] forKey:#"versionnum"];//setting the new version
[savedStock writeToFile:path atomically:YES];
}
}
And you can call the function in application launch or in your main view controller's view controller.. your choice.
hope it helps.

Get NSUserDefaults plist file from device

When testing my app on the simulator, I like the ability to edit, or even trash the apps plist file (which contains the NSUserDefaults) from the iPhone Simulator folder. This proves useful when testing (e.g. your app stores a dictionary in there, but you change the model/keys that you use for this data, and therefore need to remove the dictionary stored).
Is it possible to access this file on device (for your own app), without jailbreak?
Thanks in advance
The file is in Library/Preferences. The file is a binary plist with name <iOS application target identifier>.plist (look for the Identifier field in your app target settings), or list the directory contents:
NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
You could also load clean defaults with a #ifdef macro based on some env variable:
#ifdef TESTING
// use the code provided by tsakoyan below
#endif
If you care only for the NSUserDefaults values, this should trash/restore to global defaults all its custom data
NSDictionary *userDefDic = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
NSArray *keys = [NSArray arrayWithArray:[userDefDic allKeys]];
for (NSString *key in keys) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
[[NSUserDefaults standardUserDefaults] synchronize];

iPhone app loses file

I am writing an iPhone app – a client for some social network. The app support multiple accounts. Info about accounts are stored in a keyed archive.
A method used for saving:
- (void) saveAccounts {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:#"accounts.bin"];
// NSMutableArray *accounts = ...;
[NSKeyedArchiver archiveRootObject:accounts toFile:path];
}
A method uses for reading:
- (NSMutableArray *) loadAccounts {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:#"accounts.bin"];
NSMutableArray *restoredAccounts = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return [restoredAccounts retain];
}
The method saveAccounts is used only if some account is added/modified/deleted. The method loadAccounts is used every time the app starts. There isn't any other code that access this file.
I and one of my testers get an issue. At some moment the starts to act like accounts.bin is missing. If loadAccounts returns nil, the app offers to add an account. After I enter an account (the app must call saveAccounts), the app works normally, but when I launch it again, it asks me to add account again. The only solutions is too reinstall the app to iPhone, after reinstall it works for some time with any troubles.
We (I and my tester who get an issue) use iPhone 3G with 3.1.2.
An another tester who didn't experience this issue on his iPhone 3GS with 3.1.2.
Any ideas why this file disappears?
update
I found bug in my code. There was a code that deletes whole Document directory. Because this part of a code is a remote server related, it was hard to trace this code. Bug appeared under very rare conditions only.
Now the bug is found, and the code is corrected. wkw's answer didn't solved my problem, but it forced me to dig deeper. Thank you!
How about -- as a debugging device --verifying the contents of your Documents directory in loadAccounts or at least whenever the unarchiver returns nil. There's some code below to get the names of files in a directory. Set a breakpoint and just dump the NSArray to view the items.
If you can see that the file exists in the Docs dir, then your problem is elsewhere. Perhaps it did not successfully archive. check the return value on the call to archive the data:
if( ! [NSKeyedArchiver archiveRootObject:accounts toFile:path] ){
NSLog(#"Oooops! account data failed to write to disk");
}
Get names of files in directory:
- (NSArray*) directoryContentsNames:(NSString*) directoryPath {
NSArray* result;
{
result = [[NSFileManager defaultManager]
directoryContentsAtPath: directoryPath];
}
if( result && [result count] > 0 ){
NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];
for( NSString *name in result ){
if( ! [name isEqualToString:#".DS_Store"] )
[items addObject: name];
}
result = items;
}
return result;
}
Perhaps NSUserDefaults might be easier to use?
Also, is there a reason to use Keyed(Un)Archiver instead of NSArray's writeToFile?
if( ! [accounts writeToFile:path atomically:YES] )
; // do something since write failed
and to read the file in:
NSMutableArray *accounts = [[NSArray arrayWithContentsOfFile:path] mutableCopy];