If I want to store 2 strings, and ABContact image (or a variable to call the image) that persist even after restarting the application, which method should I use? NsMutableArray, plist or SQLite?
For very small amounts of data, like a few strings, but not an image, you could also use the much simpler NSUserDefaults. This is usually for saving preferences or some persistent data.
To save it:
[[NSUserDefaults standardUserDefaults] aString forKey:#"aStringKey];
if (![[NSUserDefaults standardUserDefaults] synchronize])
NSLog(#"not successful in writing the default prefs");
To read it:
aString = [[NSUserDefaults standardUserDefaults] stringForKey:#"aStringKey];
Again, I wouldn't go ahead and use this if what you really need is a database or a filesystem.
If you are putting in more data, and not enough to warrant an SQL database or Core Data, then I would write the data to a plist file. If all your data are objects, then I would do this:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:kSavedDocFileName];
NSMutableArray *myArrayToSave = [[NSMutableArray alloc] arrayWithCapacity: 48];
// 48 since you said in the comments that you have 48 of these
for (int i = 0; i < 48; i++) {
NSDictionary *myDict = [[NSDictionary alloc] initWithObjectsAndKeys:
sting1[i], #"firstStr", //I'm assuming your strings are in a C array.
string2[i], #"secondStr",// up to you to replace these with the right
url[i], #"myURL", //way to access your 48 pieces of data
nil];
[myArrayToSave addObject:myDict];
[myDict release];
}
if (![myArrayToSave writeToFile:path atomically:YES])
NSLog(#"not successful in saving the unfinished game");
And you can read the data:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:kMySavedDocName];
NSArray *myLoadedData = [[NSArray alloc] initWithContentsOfFile:path];
for (int i = 0; i < 48; i++) {
NSDictionary *myDict = [myLoadedData objectAtIndex:i];
string1[i] = [myDict objectForKey:#"firstStr"];
string2[i] = [myDict objectForKey:#"secondStr"];
url[i] = [myDict objectForKey:#"url"];
}
[myLoadedData release];
Make sure you don't just copy and paste this code since I just typed it in off the top of my head.
Now, if you don't want to mess with a dictionary, you could do that too. It's a little less elegant:
Save:
NSMutableArray *myArrayToSave = [[NSMutableArray alloc] arrayWithCapacity: 48 * 3];
// 48 since you said in the comments that you have 48 of these three objects
for (int i = 0; i < 48; i++) {
[myArrayToSave addObject:string1[i]];
[myArrayToSave addObject:string2[i]];
[myArrayToSave addObject:url[i]];
}
if (![myArrayToSave writeToFile:path atomically:YES])
NSLog(#"not successful in saving the unfinished game");
Load:
NSArray *myLoadedData = [[NSArray alloc] initWithContentsOfFile:path];
for (int i = 0; i < 48; i++) {
string1[i] = [myLoadedData objectAtIndex:i * 3];
string2[i] = [myLoadedData objectAtIndex:i * 3 + 1];
url[i] = [myLoadedData objectAtIndex:i * 3 + 2];
}
[myLoadedData release];
Another important note is that in all these examples, I am assuming that you are dealing with objects (48 times 3 of them). That is why you can just add them to dictionaries and arrays and save them and reload them easily. If you were dealing with non-objects, like ints, or c strings or BOOLs, then you will have to use [NSNumber ...] to turn those into objects and then [object ...] to turn them back into non-ojects to begin with.
Try some of these, and then go find your app folder in ~/Library/Application Support/iPhone Simulator/Applications folder, and look in the Documents folder, open the file you have saved, and verify its contents. It's always reassuring to be able to check the data you write out - and to see how the different methods change the contents.
Good luck!
An NSMutableArray will not persist after the application is restarted.
You may want to look at a plist that stores the two strings and a file URL pointing to the address book image that is cached in the application's Documents sandbox.
If you have lots of these to store and retrieve between application restarts, look into Core Data.
Don't do a database. It's way overkill for that little data.
A plist would be fine. You should probably not use NSUserDefaults, I think that's right on the borderline of too much data for it. Just write a plist to the documents directory inside your application, and load it up when your application restarts. You can store an NSMutableArray in a plist, but it comes back to you as an NSArray. Just re-create it as a mutable array with [NSMutableArray arrayWithArray:] and retain it.
(void)doneAceSpadeSetupClick:(id)sender {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *string1;
NSString *string2;
NSString *string3;
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"card1.plist"];
NSMutableArray *myArrayToSave = [[NSMutableArray alloc] initWithCapacity: 1];
//48 since you said in the comments that you have 48 of these
NSDictionary *myDict = [[NSDictionary alloc] initWithObjectsAndKeys:
string1, characterText.text, //I'm assuming your strings are in a C array.
string2, actionText.text,// up to you to replace these with the right
string3, objectText.text,
nil]; //iPhone simulator crashed at this line
[myArrayToSave addObject:myDict];
if (![myDict writeToFile:path atomically:YES])
NSLog(#"not successful in saving the unfinished game");
[self dismissModalViewControllerAnimated: YES];
}
Related
Im just working on what should be the "finishing touches" of my first iPhone game. For some reason, when I save with NSKeyedArchiver/Unarchiver, the data seems to load once and then gets lost or something.
I'm trying to save 2 objects with the same archiver: an NSMutableDictionary levelsPlist, and an NSMutableArray categoryLockStateArray. The are set as nonatomic, retain properties in the header file.
Here's what I've been able to deduce:
The object saved as the NSMutableDictionary always persists. It works just fine.
When I save in this viewController, pop to the previous one, and then push back into this one, the data is saved and prints as I want it to.
But when I save in this viewController, then push a new one and pop back into this one, the categoryLockState is lost.
Any idea why this might be happening? Do I have this set up all wrong? I copied it from a book months ago. Here's the methods I use to save and load.
- (void) saveGameData {
NSLog(#"LS:saveGameData");
// SAVE DATA IMMEDIATELY
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *gameStatePath = [documentsDirectory stringByAppendingPathComponent:#"gameState.dat"];
NSMutableData *gameSave= [NSMutableData data];
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:gameSave];
[encoder encodeObject:self.categoryLockStateArray forKey:#"categoryLockStateArray];
[encoder encodeObject:self.levelsPlist forKey:#"levelsPlist"];
[encoder finishEncoding];
[gameSave writeToFile:gameStatePath atomically:YES];
NSLog(#"encoded catLockState:%#",categoryLockStateArray);
}
- (void) loadGameData {
NSLog(#"loadGameData");
// If there is a saved file, perform the load
NSMutableData *gameData = [NSData dataWithContentsOfFile:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"gameState.dat"]];
// LOAD GAME DATA
if (gameData) {
NSLog(#"-Loaded Game Data-");
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:gameData];
self.levelsPlist = [unarchiver decodeObjectForKey:#"levelsPlist"];
categoryLockStateArray = [unarchiver decodeObjectForKey:#"categoryLockStateArray"];
NSLog(#"decoded catLockState:%#",categoryLockStateArray);
}
// CREATE GAME DATA
else {
NSLog(#"-Created Game Data-");
self.levelsPlist = [[NSMutableDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:kLevelsPlist ofType:#"plist"]];
}
if (!categoryLockStateArray) {
NSLog(#"-Created categoryLockStateArray-");
categoryLockStateArray = [[NSMutableArray alloc] initWithCapacity:[[self.levelsPlist allKeys] count]];
for (int i=0; i<[[self.levelsPlist allKeys] count]; i++) {
[categoryLockStateArray insertObject:[NSNumber numberWithBool:FALSE] atIndex:i];
}
}
// set the properties of the categories
self.categoryNames = [self.levelsPlist allKeys];
NUM_CATEGORIES = [self.categoryNames count];
thisCatCopy = [[NSMutableDictionary alloc] initWithDictionary:[[levelsPlist objectForKey:[self.categoryNames objectAtIndex:pageControl.currentPage]] mutableCopy]];
NUM_FINISHED = [[thisCatCopy objectForKey:kNumLevelsBeatenInCategory] intValue];
}
I can only guess that it is related to the fact that the viewController is unloaded and reloaded when you pop then push back in, but my viewDidLoad code makes no mention of either of these variables. They are called from the viewDidAppear method.
Thanks for any help you can offer!
In the program I use this method to create a file in which I can save:
-(NSString*) saveFilePath{
NSString* path = [NSString stringWithFormat:#"%#%#",
[[NSBundle mainBundle] resourcePath],
#"savingfile.plist"];
return path;}
Then I used a button to initiate the process of putting the data into the file (I first put it all into an array so that it would be easier.):
- (IBAction)save
{
NSMutableArray *myArray = [[NSMutableArray alloc] init];
[myArray addObject:name.text];
[myArray addObject:position.text];
[myArray addObject:cell.text];
[myArray addObject:office.text];
[myArray addObject:company.text];
[myArray writeToFile:[self saveFilePath] atomically:YES];
}
Finally, I loaded the information back into the textfields in the - (void)viewDidAppear method.
- (void)viewDidAppear:(BOOL)animated
{
NSMutableArray* myArray = [NSMutableArray arrayWithContentsOfFile:[self saveFilePath]];
name.text = [myArray objectAtIndex:0];
position.text = [myArray objectAtIndex:1];
cell.text = [myArray objectAtIndex:2];
office.text = [myArray objectAtIndex:3];
company.text = [myArray objectAtIndex:4];
}
For some reason, on the simulator it's working perfectly on the simulator, but not working at all when I try to run on my physical iPhone.
I think you're trying to save to a location that's read-only on iOS. It works on the simulator because the simulator doesn't totally replicate the sandboxing environment on the actual hardware.
Rather than saving to the resourcesPath you should be saving your files to the Documents directory (or the cache directory, if appropriate).
You can get a path to the documents directory as follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
There's more information at this question here: How to save file in resource folder of an app in Objective-c
im a newbie to iOS programming and getting my hands dirty on it. I searched on the net for some plist help and got something and understood it and used it but now im stuck at a point. I have searched alot for this problem. but im just unable to find the correct answer for me.
Problem:
My UI has just 2 text Fields and 1 Save Button.
1 textfield takes string while the other takes number (int) as input.
my plist has 1 dictionary item, that has 1 string item and 1 int item. thats it.
i take input in 2 UITextViews from user and save them into this plist via a save Button.
The problem is that whenever i enter new values and press the save button, it overwrites the old plist data.
I found out that i need to read the dictionary, append new values to it and then save it back in order to get my desired output. but im not able to grab this concept and put it into code. some code with explanation would really help.
my save button works like this:
-(IBAction)saveit
{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"data.plist"];
// set the variables to the values in the UITextField text1 n text2 respectively
self.personName = text1.text;
num = (int)text2.text;
// create dictionary with values in UITextFields
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: personName, num, nil] forKeys:[NSArray arrayWithObjects: #"name", #"phone", nil]];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check is plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
}
else
{
NSLog(#"Error in saveData: %#", error);
// [error release];
}
}
Tthis code is working fine just that it is over writing new values. please help.
Your plist will return a dictionary.Retrieve that dictionary as NSMutableDictionary and add your new key pair value to that dictionary and then save it.
Save the data as an array of dictionaries like this
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"data.plist"];
//Load the original file
NSMutableArray *arr;
if([[NSFileManager defaultManager] fileExistsAtPath:plistPath])
//File exist load
arr = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];
else
//File does not exist create
arr = [[NSMutableArray alloc] init];
//Create dictionary
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: personName, num, nil] forKeys:[NSArray arrayWithObjects: #"name", #"phone", nil]];
//Append to arr
[arr addObject:plistDict];
//Save to file
[arr writeToFile:plistPath atomically:YES];
I've been stuck on this for ever and I finally figured it out and now just out of the blue it stopped working again...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/scoreCards.dgs",documentsDirectory];
NSMutableArray *savedArrayOfScorecards = [[NSMutableArray alloc]init];
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
[savedArrayOfScorecards addObject:currentScoreCard];
[savedArrayOfScorecards writeToFile:filePath atomically:YES];
The file scoreCards.dgs is not even getting created...
What am I doing wrong?
There could be a couple things going wrong here.
1) The kind of data you're storing in the array might not be encodable or archive-able to a file. And the code snippet you included doesn't give a good hint as to what kind of data you're trying to save. If you have custom objects in your array (i.e. things that are not NSString, NSNumber, NSDate, etc.), then that's definitely the problem. There are plenty of questions here on StackOverflow that might help you solve this issue.
2) Your array's filepath could be bogus. For example, you're not checking to see if "documentsDirectory" is nil or valid or writeable.
3) Also possible, but not likely, "savedArrayOfScorecards" might be a nil array. You should do error checking to make sure "savedArrayOfScorecards" was instantiated and that there is more than one object in the array.
Your problem is, that although you create an array, before reading the file it is getting nil-ed on your call to:
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
So, because this savedArrayOfScorecards is now nil, your call to write it to a file is not doing anything.
You should load the array to another variable, and check it being nil, and create the new array only if the one read from the file is nil. Something like this:
NSMutableArray *savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
if (!savedArrayOfScorecards) {
savedArrayOfScorecards = [[NSMutableArray alloc]init];
}
Are you sure the file exists when loading it?
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
This line creates a new NSMutableArray from the file. If the file does not exist, it returns nil. writeToFile is then sent to nil and nothing would happen.
Add a check to see if it's nil and create a new array if it is:
NSMutableArray *savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
if(savedArrayOfScorecards == nil) savedArrayOfScorecards = [NSMutableArray array];
[savedArrayOfScorecards addObject:currentScoreCard];
[savedArrayOfScorecards writeToFile:filePath atomically:YES];
NSMutableArray is not a property-list-compliant format. You must use an NSArchiver to make it plist compliant.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/scoreCards.dgs",documentsDirectory];
NSMutableArray *savedArrayOfScorecards = [[NSMutableArray alloc]init];
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
[savedArrayOfScorecards addObject:#"ALLLAALLAAALLA"];
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archive = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
[archive encodeObject:savedArrayOfScorecards forKey:#"Scorecards"];
[archive finishEncoding];
BOOL result = [data writeToFile:filePath atomically:YES];
NSLog(result ? #"YES" : #"NO");
The correct answers are already here, just adding a better solution:
NSFileManager* fileManager = [NSFileManager defaultManager];
NSMutableArray* array;
if ([fileManager fileExistsAtPath:filePath]) {
array = [NSMutableArray arrayWithContentsOfFile:filePath];
NSAssert(array != nil, #"Invalid data in file.");
}
else {
array = [[NSMutableArray] alloc] init];
}
[array addObject:currentScoreCard];
[array writeToFile:filePath atomically:YES];
Well, I know it may sounds basic, but I have literally been looking everywhere and could not find a straight answer to that. I am trying to save location coordinates to a file every time I get an update - sounds simple.... I have two problems: one is with the data type (writeToFile seems to save only NSData) and the other one is with appending to the end of the file. I tried to use NSKeyedArchiver but it wrote a bunch of garbage and I could not find how to append to the end of file with it.
Here is my code - if you could help I would greatly appreciate that. Thanks!
....
NSMutableArray *array = [[NSMutableArray alloc] init];
NSNumber *numLat = [NSNumber numberWithFloat:location.coordinate.latitude];
NSNumber *numLong = [NSNumber numberWithFloat:location.coordinate.longitude];
[array addObject:numLat];
[array addObject:numLong];
NSFileHandle *file;
file = [NSFileHandle fileHandleForUpdatingAtPath: #"./location.txt"];
if (file == nil)
NSLog(#"Failed to open file");
[file seekToEndOfFile];
[file writeData: array]; //BTW - this line doesn't work if I replace array with numLat which is an NSNumber - unlike what many people have said in various discussions here
OR - for the saving to file portion (last two lines):
NSString *path = #"./location.txt";
[NSKeyedArchiver archiveRootObject:array toFile:path];
// Get the path to the Documents (this is where your app saves data)
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsPath = [searchPaths objectAtIndex: 0];
[array writeToFile:[documentsPath stringByAppendingPathComponent:#"location"] atomically:YES];
To load the data back into the array, use
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsPath = [searchPaths objectAtIndex: 0];
array = [[NSMutableArray alloc] initWithContentsOfFile:[documentsPath stringByAppendingPathComponent:#"location"];