My app is a list of items that the user can rate and take notes on etc. All of these items (dictionaries that store the user ratings and notes) are stored in a plist which is moved to the documents directory when the app first loads (if the plist file already exists in documents directory it skips this step). Now I've had a few reviews and people have been saying there should be more items in the lists. Now I'm trying to figure out how to go about adding new items to the plist. If I add them to the existing plist the users will never see the new items because the app checks to see if the database already exists in the documents directory. So they will never receive the new list. So I planned on creating a new plist with items in it and appending each new item to the old plist if the item doesn't already exist (because the user can add items themselves so I don't want to have duplicate entries in case the user added an item with the same name). My dilemma is I'm not sure where exactly/how to do the plist merging. Do I do it in the App Delegate? Do I do it in the viewDidLoad of the main view? How do I stop it from checking every time the app loads if it has already added the new items to the old plist? I know there are different ways to do it. I'm just looking for the easiest and "less load heavy on the app" way of doing it.
Ok. I solved it thanks to a couple people in one of the iOS chats. So basically inside the application didFinishLaunchingWithOptions method in my AppDelegate.m I loaded both plists into 2 different NSArrays array1 and array2, I also created an NSMutableArray array3 which I loaded with the original plist that was in the users documents directory.Then I looped through them both checking to make sure I didn't add any duplicate items based on their Name_Key (because the items in the arrays are dictionaries). If the items didn't match then I would add them to the 3rd array (array3). Then I saved array3 out to the original plist file path. Then I set an integer inside the user defaults so It would only perform this task once. I didn't want the app to go through this EVERY time the user opened the app. Below is the code I used.
int haveMergedPlist = [[NSUserDefaults standardUserDefaults] integerForKey:#"merged plist"];
if (haveMergedPlist < 1){
// I used a integer rather than a BOOL because if I ever want to perform this operation again with another app update all I have to do is change the number.
// merge code here
//new plist
NSString *newPlistPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"NewPlist.plist"];
NSArray *newPlistArray = [[NSArray alloc]initWithContentsOfFile:newPlistPath];
//original plist in user documents directory
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [searchPaths lastObject];
NSString *originalPlistPath = [documentsDirectory stringByAppendingPathComponent:#"originalPlist.plist"];
NSArray *originalPlistArray = [[NSMutableArray alloc]initWithContentsOfFile:originalPlistPath];
NSMutableArray *combinedPlistArray = [[NSMutableArray alloc]initWithContentsOfFile:originalPlistPath];
// Loop through both plists arrays and add if it does not match/exist.
BOOL itemExists = YES;
for (NSDictionary *newItem in newPlistArray) {
for (NSDictionary *originalItem in originalPlistArray) {
NSString * newItemNameString = [[newItem objectForKey:NAME_KEY] lowercaseString];
NSString * origItemNameString = [[originalItem objectForKey:NAME_KEY] lowercaseString];
//Compare lowercase strings and if they don't match then add them to the original plist array.
if ([newItemNameString isEqualToString:origItemNameString]) {
itemExists = YES;
break;
} else {
itemExists = NO;
//doesn't match so add it
}
}
if (itemExists == NO) {
[combinedPlistArray addObject:newItem];
NSLog(#"newItem added");
itemExists = YES;
}
}
//Write Array With New Items Added to documents directory
NSString *writeableDBPath = [documentsDirectory stringByAppendingPathComponent:#"originalPlist.plist"];
[combinedPlistArray writeToFile:writeableDBPath atomically:YES];
//set integer to 1 in user defaults so it only merge's once
//I comment this out while getting the loops to work otherwise you will have to delete the app out of the simulator or the phone to make sure its working every time.
[[NSUserDefaults standardUserDefaults] setInteger:1 forKey:#"merged plist"];
}
Related
even after so many research i haven't found a solution for this question. I am currently working on a app which uses 3 view controllers for Registration with a log out button. the last view controller has the Register button which saves all the details of registration in a web service. But if the user has filled the two view forms and logs out. The two view filled forms field should be saved in the local memory and wen the user logs it again the pre filled forms should load the fields saved in internal memory just to continue the Registration for webservice. Any idea how to implement this sort of functionality
As others have said, NSUserDefaults will suffice for what you need.
NSUserDefaults *registrationInfo = [NSUserDefaults standardUserDefaults];
Guessing you have text fields with the info you need. So pull out the text and save to a key like this.
[registrationInfo setObject:self.someTextFieldName.text forKey#"firstTextField"];
After repeating this for every text field(use different key names though), call this [registrationInfo synchronize];
To pull the data out, you open the defaults again just like the first line. And to retrieve a specific key: NSString *firstTextField = [registrationInfo objectForKey:#"firstTextField"];
To make this easier, you can also put all of your strings in an array or dictionary, and then add that as an object in your defaults. Then you only have to set/get once.
If you have large amount of data to save use CoreData else you NSUserDefaults to save it.
I suggest you to use PLIST There are mainly three steps to do this.
1) Generate .plist file.
NSError *error1;
BOOL resourcesAlreadyInDocumentsDirectory;
BOOL copied1;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath1 = [documentsDirectory stringByAppendingString:#"/epub.plist"];
resourcesAlreadyInDocumentsDirectory = [fileManager fileExistsAtPath:filePath1];
if(resourcesAlreadyInDocumentsDirectory == YES) {
} else {
NSString *path1 = [[[NSBundle mainBundle] resourcePath] stringByAppendingFormat:#"/epub.plist"];
copied1 = [fileManager copyItemAtPath:path1 toPath:filePath1 error:&error1];
if (!copied1) {
NSAssert1(0, #"Failed to copy epub.plist. Error %#", [error1 localizedDescription]);
}
}
2) Try to read(open) it.
NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath1];
3) write data to plist file.
[dict setObject:[NSNumber numberWithInt:value] forKey:#"value"];
[dict writeToFile:path atomically:YES];
This is a simple way to use it. I suggest to use .plist file in place of NSUserDefaults.
How can I append values to an existing key in NSDictionary from a plist?
What I have is basically a plist saved in disc with a few keys and values, the keys are the names of some students with one initial value. What I’m trying to do that doesn't work is to append more items to existing keys/Students by reading the plist, adding it to a temporary NSDictionary, appending a temporary array to an existing key in the plist but when I save the plist back it doesn’t work, it only saves the last two items and basically deletes the initial value for that key.
Here is the code that I’m using…
- (IBAction)testing:(id)sender
{
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [dirPaths objectAtIndex:0];
NSString *fullPath = [documentsPath stringByAppendingPathComponent:#"studentsRecords.plist"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath];
if(fileExists)
{
NSMutableDictionary *dictionaryRecords = [[NSMutableDictionary alloc]initWithContentsOfFile:fullPath];
NSLog(#"Items for Nathan: %#",[dictionaryRecords objectForKey:#"Nathan"]);// here the output is, Items for Nathan: Ruler
// which make sense since I only had one initial record for Nathan in my plist,
// Here, I want to add two more items to the key Nathan by appending an arry to
// NSMutableDictionary (dictionaryRecords) but it doesnt work
NSArray *tempArray = #[#"NoteBook", #"Pencil"];
[dictionaryRecords setObject:tempArray forKey:#"Nathan"];
[dictionaryRecords writeToFile:fullPath atomically:YES];
// when I check the plist after saving this, I only see the last two values, NoteBook and Pencil
// instead of the three that I'm expecting, Ruler, NoteBook and Pencil
}
}
What am I missing here?
Thanks a lot in advance.
[dictionaryRecords setObject:tempArray forKey:#"Nathan"] replaces whatever the previous value was with tempArray.
If you want to add to the existing array, you have to retrieve it, make a mutable copy, and append to it.
Here is how I made it work. I hope I didn't over complicated things here but for some reason I had to use two arrays, an NSArray and an NSMutableArray, I would think that the NSMutableArray would be enough but it didn't work.
- (IBAction)testing:(id)sender
{
// get directory path
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [dirPaths objectAtIndex:0];
// create plist
NSString *fullPath = [documentsPath stringByAppendingPathComponent:#"studentsRecords.plist"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath];
// if file exists create dictionary
if(fileExists)
{
// add items from plist to dictionary
NSDictionary *dictionaryExistingRecords = [[NSDictionary alloc]initWithContentsOfFile:fullPath];
// make a copy if dictinary with existing items
NSMutableDictionary *dictionaryNewRecords = [dictionaryExistingRecords mutableCopy];
// array to hold existing values from a spcecified key
NSArray *arrayWithExistingKey = [dictionaryExistingRecords objectForKey:#"Nathan"];
// mutable array to add existing and be able to insert new items
NSMutableArray *arrayOldAndNewItems = [NSMutableArray arrayWithArray:arrayWithExistingKey];
// insert new items to array
[arrayOldAndNewItems addObject:#"Snow Pans"];
// add old and new items to specified key
[dictionaryNewRecords setObject:arrayOldAndNewItems forKey:#"Nathan"];
// save new records to plist
[dictionaryNewRecords writeToFile:fullPath atomically:YES];
}
}
#end
So this is my first time trying to save data in an iOS app. I've pieced together this code from various answers on this site in order to save a high score for a game I'm making. I created a plist named saves.plist (in my Supporting Files folder) and added a row of key #"bestScore" and type Number. The test log returns that the save is successful, and everything works; however, when I go to look at the plist after, nothing seems to have changed (the value of bestScore is 0). Am I saving to a different plist that is automatically created in my code? If this is the case, what is the point of being able to create plists in Xcode, and what is the best practice to use here as far as where/how to create/store/access plists?
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.fm = [NSFileManager defaultManager];
self.destPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];//Documents directory
self.destPath = [self.destPath stringByAppendingPathComponent:#"saves.plist"];
// If the file doesn't exist in the Documents Folder, copy it.
if (![self.fm fileExistsAtPath:self.destPath]) {
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"saves" ofType:#"plist"];
[self.fm copyItemAtPath:sourcePath toPath:self.destPath error:nil];
}
}
- (void)saveBestScore{
NSNumber *bestScoreBox = [NSNumber numberWithUnsignedLong:self.bestScore];
NSDictionary *data = #{bestScoreBox: #"bestScore"};
BOOL successful = [data writeToFile:self.destPath atomically:YES];
successful ? NSLog(#"YES") : NSLog(#"NO");
}
When you say
when I go to look at the plist after, nothing seems to have changed
(the value of bestScore is 0)
Do you mean looking at the plist in xcode project files ? You have copied the plist into a device directory and therefore you wont be able to see the change in xcode.
If you are using simulator, you can access the changed plist at:
~/Library/Application Support/iPhone Simulator/<Simulator Version>/Applications/<application>/Documents/
One easy way of storing score is to use NSUserDefault, which is a dictionary like persistence store for each application.
Set Score:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:#(score)
forKey:#"score"];
[userDefaults synchronize];
Get Score:
int score = [[[NSUserDefaults standardUserDefaults] objectForKey:#"score"] intValue];
UPDATE:
rmaddy mentioned NSUserDefaults supports setInteger:forKey and integerForKey: Therefore you dont need to wrap the score into a NSNumber
When you write an NSDictionary to a plist using writeToFile:, the keys and values in the dictionary must follow strict rules. All keys must be NSString objects and all values must be property values (NSString, NSNumber, NSDate, NSData, etc.).
The problem you have is your dictionary has a key that is an NSNumber, not an NSString.
It appears you actually create the dictionary incorrectly. The syntax is:
#{ key : value, key : value, ... }
Change your code to:
NSDictionary *data = #{ #"bestScore" : bestScoreBox }; // key : value
Side note - your last line should be:
NSLog(#"%#", successful ? #"YES" : #"NO");
It's not good practice to use the ternary operator to run two different commands. It's meant to return one of two values.
I have an app that allows the user to build a simple playlist which is stored as an array of MPMediaItems. When the app quits or enters the background, I want to store the playlist in a file and retrieve it when the user opens the app again.
Should I just be saving an id in a file as opposed to a whole MPMediaItem object?
Also, as the user's library may have changed how would I compare the saved list against the iPod library, do I loop through and run a bunch of queries?
Save this array into plist file, which is a effective way to save and read from local file.
Here in below are metheds to create, save, read data from plist. Hope it is helpful for you.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doc = [paths objectAtIndex:0]; //path of document folder
NSString *path = [doc stringByAppendingPathComponent:#"nameOfPlist.plist"]; //get the path of your plist in document folder.
NSDictionary* dic = [NSDictionary dictionaryWithObjectsAndKeys:#"123456",#"number", nil]; //save your data into a dictionary or array.
[dic writeToFile:path atomically:YES]; //then write the dic/array into the plist file.
NSDictionary* dic2 = [NSDictionary dictionaryWithContentsOfFile:path]; //read data from plist.
NSLog(#"dic2 is:%#",[dic2 objectForKey:#"number"]);
I am making an RPG game for the iPhone and everything is working out great but I need to know how to save my game level so that even if the user were to close the app running in the background the entire game wouldn't start over again. I was even thinking of bringing back old style gaming and making it so that you have to enter a password to start from where you left off. But even then I wouldn't know how to save the game properly. Plus even if I did save the game how would I be able to make it stay saved even when the app closes completely? So far I have tried adding save data code to the AppWillTerminate line but still nothing. Any help is appreciated.
I'm not sure if you want to save which level the user was on, or if you want to save the game state. If you simply want to save which level the user was on, you should go with #EricS's method (NSUserDefaults). It's a little more complicated to save game state. I would do something like this:
//Writing game state to file
//Some sample data
int lives = player.kLives;
int enemiesKilled = player.kEnemiesKilled;
int ammo = player.currentAmmo;
//Storing the sample data in an array
NSArray *gameState = [[NSArray alloc] initWithObjects: [NSNumber numberWithInt:lives], [NSNumber numberWithInt:enemiesKilled], [NSNumber numberWithInt:ammo], nil];
//Writing the array to a .plist file located at "path"
if([gameState writeToFile:path atomically:YES]) {
NSLog(#"Success!");
}
//Reading from file
//Reads the array stored in a .plist located at "path"
NSArray *lastGameState = [NSArray arrayWithContentsOfFile:path];
The .plist would look like this:
Using an array would mean that upon reloading the game state, you would have to know the order that you stored the items in, which isn't that bad, but if you want a more reliable method, you could try using an NSDictionary like this:
//Writing game state to file
//Some sample data
int lives = player.kLives;
int enemiesKilled = player.kEnemiesKilled;
int ammo = player.currentAmmo;
int points = player.currentPoints;
//Store the sample data objects in an array
NSArray *gameStateObjects = [NSArray arrayWithObjects:[NSNumber numberWithInt:lives], [NSNumber numberWithInt:enemiesKilled], [NSNumber numberWithInt:points], [NSNumber numberWithInt:ammo], nil];
//Store their keys in a separate array
NSArray *gameStateKeys = [NSArray arrayWithObjects:#"lives", #"enemiesKilled", #"points", #"ammo", nil];
//Storing the objects and keys in a dictionary
NSDictionary *gameStateDict = [NSDictionary dictionaryWithObjects:gameStateObjects forKeys:gameStateKeys];
//Write to file
[gameStateDict writeToFile:path atomically: YES];
//Reading from file
//Reads the array stored in a .plist located at "path"
NSDictionary *lastGameState = [NSDictionary dictionaryWithContentsOfFile:path];
The dictionary .plist would look like this:
To save the level:
[[NSUserDefaults standardUserDefaults] setInteger:5 forKey:#"level"];
To read the level:
NSInteger level = [[NSUserDefaults standardUserDefaults] integerForKey:#"level"];
I would set it whenever the user enters that level. You could wait until you are sent into the background, but there's really no point in waiting.