Need advice for merging two plists into an array - iphone

Currently, I have a tableView which is loaded from the following array called exerciseArray:
if (exerciseArray == nil)
{
NSString *path = [[NSBundle mainBundle]pathForResource:#"data" ofType:#"plist"];
NSMutableArray *rootLevel = [[NSMutableArray alloc]initWithContentsOfFile:path];
self.exerciseArray = rootLevel;
[rootLevel release];
}
However, I want an option to let the user add their own data which will need to be displayed in this same table view. I can either add that data to the original plist but I think it will be better to create a separate plist that will have the user added data.
So after the user adds data, the I will need to combine the 2 plist data next time. Any idea how to implement this? The user added data will need to match up with original plist structure which looks like:
The new plist should only have ExerciseName and musclename, but I need to merge the two somehow.

The user-defined Property List should be stored in a file somewhere in the Documents directory. Usually, for something like this, I would suggest creating a copy of the default Plist that ships with the app to the Documents directory, then modifying and loading this file as needed. Copying the resource to the documents directory can be done as follows:
NSString * documentsPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSString * docPlist = [documentsPath stringByAppendingPathComponent:#"data.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:docPlist]) {
NSString * resPlist = [[NSBundle mainBundle]pathForResource:#"data" ofType:#"plist"];
[[NSFileManager] defaultManager] copyItemAtPath:resPlist
toPath:docPlist
error:nil];
}
NSMutableArray * root = [[NSMutableArray alloc] initWithContentsOfFile:docPlist];
The above code simply finds the path to the plist in the documents directory, and checks if it exists. If it does not, it copies the data.plist resource over to the documents directory. Once this is done, it loads the root element into a mutable array, which you can then use and modify using the standard addObject: and removeObject: methods.
Finally, to save any modified data, simply write the root array back to the plist file as follows:
[root writeToFile:docPlist atomically:YES];
The only delemma with this is, while in the process of developing the app, you may wish to change or modify some data in the data.plist resource. In order for the app to copy this newly modified Property List to the documents directory, you will need to uninstall it completely from the iOS Simulator, then re-compile and run.
Edit: In order to add an exercise to an existing muscle, just do something like this:
// find the exercise dictionary
int muscleIndex = 0;
NSMutableDictionary * muscleDict = nil;
NSString * muscleName = #"Neck";
NSString * exerciseName = #"Nod your head a billion times";
// find the right muscle
for (int i = 0; i < [root count]; i++) {
NSDictionary * muscle = [root objectAtIndex:i];
if ([[muscle objectForKey:#"muscleName"] isEqualToString:muscleName]) {
muscleIndex = i;
muscleDict = [NSMutableDictionary dictionaryWithDictionary:muscle];
}
}
NSMutableArray * exercises = [NSMutableArray arrayWithArray:[muscleDict objectForKey:#"exercises"]];
[exercises addObject:exerciseName];
[muscleDict setObject:exercises forKey:#"exercises"];
[root replaceObjectAtIndex:muscleIndex withObject:muscleDict];
...
// save root here

Related

Add & Remove Array Items inside a plist

I have problem with adding and removing items in an array inside a plist file in XCODE.
I'm able to read the array by following code:
// Path to the plist (in the application bundle) ------>>>>>
NSString *path = [[NSBundle mainBundle] pathForResource:
#"Fav" ofType:#"plist"];
// Build the array from the plist ------>>>
NSDictionary *favs = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
Resepies = [favs objectForKey:#"Root"];
And this my plist structure
The senario is to let the user add and remove specific item from the array at a time.
try this code -
NSString *path = [[NSBundle mainBundle] pathForResource: #"Fav" ofType:#"plist"];
// Build the array from the plist ------>>>
NSDictionary *favs = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
Resepies = [favs objectForKey:#"Root"];
[Resepies addObject:addYourObjectHere];
//then add this array into dictonary ;
[favs setObject:Resepies forKey:#"Root"];
// now write dictionary to plist
[favs writeToFile:yourPlistName atomically:YES];
You can't modify files in the application bundle, which is what your code above is attempting to do.
If the file is supposed to be modifiable, you need to move it into the documents folder first (say, on first run) and then read / write to that one subsequently. There are plenty of questions dealing with how to do this, for example : create plist and copying plist to document directory

how to add values to a dictionary inside a dictionary of a property list

I am currently creating a controller class for my plist.in this plist I have a root dictionary that has several types in it (Number, String and Dictionary), In my controller class I check for a plist then add it to the documents so I can read and write to it.
From here I read whats in my current plist and pass those values over to tempvars I have set up in this class.
This is what my read method looks like in my plist controller class.
-(void) readPlistData
{
// Data.plist code
// 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:#"EngineProperties.plist"];
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"EngineProperties" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
// assign values
self.protocolSignature = [temp objectForKey:#"Protocol"];
self.requestNumber = [temp objectForKey:#"RequestNumber"];
//How do I add the dictionary values here?
}
The reason I put the data into variables is because latter I am going to use these values to test against checks I want to perform against my db.. making sure of things like i am receiving the correct request number etc.
UPDATE:: my idea to add them to the dictionary inside the root dictionary would be something like this. which i think is not even close but it might give you a better clue to what I am trying to do.
self.cacheValue = [temp objectForKey:#"Cache Value"];
self.manufacturers = [cacheValue objectForKey:#"Manufacturers"];
self.models = [cacheValue objectForKey:#"Model"];
self.subModels = [cacheValue objectForKey:#"SubModels"];
any help would be greatly appreciated.
I believe you want to do the following:
Define your cacheValue property in the .h as a mutable dictionary.
NSMutableDictionary *cacheValue;
Serialize the plistXml as a NSMutableDictionary:
// This is the root Dictionary
NSMutableDictionary *temp = (NSMutableDictionary *)[NSPropertyListSerialization propertyListWithData:plistXML options:NSPropertyListMutableContainersAndLeaves format:NSPropertyListXMLFormat_v1_0 error:&error];
Since everything is mutable, you can now read, update, insert, delete any part of the dictionary or its subcontents. For instance, grabbing the Mutable Dictionary "Cache Value" is just:
self.cacheValue = [temp objectForKey:#"Cache Value"];
Remember to check that the object is not nil in case there isn't a value for the key. The key needs to be exactly as it appears in the plist.
Updating a value in the Mutable Dictionary is easy:
[self.cache setValue:#"New Value" forKey:#"Sub"];
And finally, to save the changes in the root Mutable Dictionary back to the plist:
/*
The flag "atomically" specifies whether the file should be written atomically or not.
If flag is YES, the receiver is written to an auxiliary file, and then the auxiliary file is renamed to path.
If flag is NO, the dictionary is written directly to path.
The YES option guarantees that path will not be corrupted even if the system crashes during writing.
*/
[self.temp writeToFile:plistPath atomically:YES];
Hope this helps, cheers!

Need help creating .plist in app

My app ships with a .plist that looks like this:
I want the user to be able to add a custom exerciseName.
So I need to create a new .plist in the user's document folder that mimics this format. Can anyone help me with this?
I need something like this (pseudo code)
if (userData == nil)
{
then create the .plist file;
setup the .plist to mimic the format of the img above.
}
now save exerciseName appropriately.
Update:
if (exerciseArray == nil)
{
NSString *path = [[NSBundle mainBundle]pathForResource:#"data" ofType:#"plist"];
NSMutableArray *rootLevel = [[NSMutableArray alloc]initWithContentsOfFile:path];
self.exerciseArray = rootLevel;
[rootLevel release];
}
What you will want to do is load the Plist into an NSDictionary, and encode that NSDictionary back to a Plist file in your applications document folder. In your applicationDidFinishLoading: method, I would do something like this:
NSString * documentFile = [NSHomeDirectory() stringByAppendingFormat:#"/Documents/myPlist.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:documentFile]) {
// create a copy of our resource
NSString * resPath = [[NSBundle mainBundle] pathForResource:#"myPlist" ofType:#"plist"];
// NOTE: replace #"myPlist" with the name of the file in your Resources folder.
NSDictionary * dictionary = [[NSDictionary alloc] initWithContentsOfFile:resPath];
[dictionary writeToFile:documentFile atomically:YES];
[dictionary release];
}
Then, when you want to add an item, you want to use an NSMutableDictionary to modify and save the existing plist in the app's documents directory:
- (void)addExercise {
NSMutableDictionary * changeMe = [[NSMutableDictionary alloc] initWithContentsOfFile:documentFile];
... make changes ...
[changeMe writeToFile:documentFile atomically:YES];
[changeMe release];
}
To make changes, you will need to find the sub-dictionary containing the array of exercises. Then use the setObject:forKey: method on the NSMutableDictionary to set a new array containing a new list of exercises. This might look something like this:
NSMutableArray * list = [NSMutableArray arrayWithArray:[changeMe objectForKey:#"list"]];
NSArray * exercises = [[list objectAtIndex:10] objectForKey:#"exercises"];
NSDictionary * newExercise = [NSDictionary dictionaryWithObject:#"Type a LOT" forKey:#"exerciseName"];
exercises = [exercises arrayByAddingObject:newExercise];
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithDictionary:[list objectAtIndex:10]];
[dict setObject:exercises forKey:#"exercises"];
[list replaceObjectAtIndex:10 withObject:dict];
[changeMe setObject:list forKey:#"list"];
Once you make your change, it is important to remember to write changeMe to the plist file in the documents directory.
The easiest way to read and write property lists is to use the NSArray or NSDictionary classes. Your screenshot appears to be an array at the top level, so I will use that assumption for my examples.
First you need paths to the user file and original file.
NSString *pathToUserFile; // Get a path to the file in the documents directory
NSString *pathToDefaultFile; // Get a path to the original file in the application bundle
You then attempt to load the
NSMutableArray *userData;
NSArray *temporary = [NSArray arrayWithContentsOfFile:pathToUserFile];
if(!temporary) temporary = [NSArray arrayWithContentsOfFile:pathToDefaultFile];
Since it appears that you are using multiple layers containers, I am assuming that you will need the innermost arrays and dictionaries to be mutable. The normal initialization of NSMutableArray will not do this, so you need to use CFPropertyListCreateDeepCopy with the options set to have mutable containers.
userData = (NSMutableArray *)CFPropertyListCreateDeepCopy(NULL,(CFArrayRef)temporary,kCFPropertyListMutableContainers);
You now have a mutable object representing your data. You can add objects or modify existing objects the same way you handle any array, but you can only add strings, numbers, data objects, dictionaries, arrays, and dates, since those are the only types valid in property lists.
[userData addObject:newDataObject];
Finally, you write the data out to the file in the documents directory. The writeToFile:atomically: method will attempt to write out a property list, and return YES if successful. It will fail and return NO if the file could not be written, or if the contents are not all valid property list objects.
if(![userData writeToFile:pathToUserFile atomically:YES]) {
NSLog(#"Error writing to file");
}

Adding data to the plist dynamically and populating the tableview cells

I am developing an application where i have created the plist, and i am adding data to it..but what is happening is that everytime the data is overwritten and the previous data is lost. I mean suppose i add one name called rocky, next time when i add rock, rocky gets overwritten with rock, but what i want is my plist should contain both rocky and rock and so on...I am adding the data in plist by user entry....
here is my code below..
-(IBAction) myplist:(id) sender//the data is saved in a plist by clicking on this button
{
NSLog(#"mylist Clicked");
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:searchLabel.text];
// 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"];
// This writes the array to a plist file. If this file does not already exist, it creates a new one.
[array writeToFile:plistPath atomically: TRUE];
}
I think this will serve your purpose with a slight modification to your code.
NSLog(#"mylist Clicked");
NSMutableArray *array = [[NSMutableArray alloc] init];
// 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"];
//This copies objects of plist to array if there is one
[array addObjectsFromArray:[NSArray arrayWithContentsOfFile:plistPath]];
[array addObject:searchLabel.text];
// This writes the array to a plist file. If this file does not already exist, it creates a new one.
[array writeToFile:plistPath atomically: TRUE];
Try to use a sequence to store data to pList.
1., retrieve old data from pList into a NSMutableDictionary/NSMutableArray
2., add a new record into the NSMutableDictionary/NSMutableArray
3., write to file
You cant append data to Plist. Since you are doing writeToFile each time , the plist file gets re-written. So the data u stored initially will not be there in it. The only other way to achieve wat u desire is to retrieve the array of data from the plist. Then add ur new data object to the array. Write the plist file to disk again with the new appended array.
Hope this helps.

How do I append a new item to a plist?

In my iPhone app, I have two plist files to store "Themes". One is a read-only file containing default themes, and one contains custom Themes that the user has created. I'm using plist files because it's very easy for me to read from the plist and create new Theme objects.
My plist is an array of dictionary objects.
Is there any easy way to append a new dictionary object to my plist file? Or do I need to read the file into memory, append the new dictionary object, and write it back to the filesystem?
Thanks!
With Cocoa, you need to read the file into memory, append the new dictionary object, and write it back to the filesystem. If you use an XML plist, you could pretty easily parse it and incrementally write to the file, but it'd also be quite a bit bigger, so it's unlikely to be worth it.
If rewriting the plist is taking too long, you should investigate using a database instead (perhaps via Core Data). Unless the file is huge, I doubt this will be an issue even with the iPhone's memory capacity and flash write speed.
(I copied this for those who don't want to click a link from a similar question I answered here: A question on how to Get data from plist & how should it be layout)
Here are two methods to read and write values from a plist using an NSDictionary:
- (NSMutableDictionary*)dictionaryFromPlist {
NSString *filePath = #"myPlist.plist";
NSMutableDictionary* propertyListValues = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
return [propertyListValues autorelease];
}
- (BOOL)writeDictionaryToPlist:(NSDictionary*)plistDict{
NSString *filePath = #"myPlist.plist";
BOOL result = [plistDict writeToFile:filePath atomically:YES];
return result;
}
and then in your code block somewhere:
// Read key from plist dictionary
NSDictionary *dict = [self dictionaryFromPlist];
NSString *valueToPrint = [dict objectForKey:#"Executable file"];
NSLog(#"valueToPrint: %#", valueToPrint);
// Write key to plist dictionary
NSString *key = #"Icon File";
NSString *value = #"appIcon.png";
[dict setValue:value forKey:key];
// Write new plist to file using dictionary
[self writeDictionaryToPlist:dict];
This is how I am appending data to the plist:
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
[array addObject:countdownLabel.text];
[array writeToFile:[self dataFilePath] atomically:YES];
[array release];
}
else
{
NSArray *array = [NSArray arrayWithObject:countdownLabel.text];
[array writeToFile:filePath atomically:YES];
}