I have two plist files that I'm using as datasources to create NSArray and NSDictionaries in my app.
I want the output of the CSV file to look like:
exerciseName, muscleGroup, description
Barbell Curl, Biceps, This is a bicep exercise
The problem is, I need to first combine two NSDictionaries I have. One has exerciseName and muscleName, while the other has exerciseDescription. But I need to have one array of dictionaries for each exercise obejct that has all 3 keys.
I'm using the following code to build the main NSMutableArray
if (muscleArray == nil)
{
NSString *path = [[NSBundle mainBundle]pathForResource:#"data" ofType:#"plist"];
NSMutableArray *rootLevel = [[NSMutableArray alloc]initWithContentsOfFile:path];
self.muscleArray = rootLevel;
}
NSMutableArray *arrayForSearching = [NSMutableArray array];
for (NSDictionary *muscleDict in self.muscleArray)
for (NSDictionary *excerciseDict in [muscleDict objectForKey:#"exercises"])
[arrayForSearching addObject:[NSDictionary dictionaryWithObjectsAndKeys:
[excerciseDict objectForKey:#"exerciseName"], #"exerciseName",
[muscleDict objectForKey:#"muscleName"], #"muscleName", nil]];
self.exerciseArray = arrayForSearching;
I'm using the following code to build the NSDictionary which has the exerciseDescription key
NSString *exerciseNameString =self.exerciseName;
NSString *path = [[NSBundle mainBundle] pathForResource:#"ExerciseDescriptions" ofType:#"plist"];
NSDictionary *exerciseDescription = [NSDictionary dictionaryWithContentsOfFile:path];
NSString *description = [exerciseDescription objectForKey:exerciseNameString];
You obviously know how to iterate through and create dictionaries, so what's the problem?
Iterate through exerciseArray and add an exercise description to each of its dictionaries. However this seems wildly inefficient and you should probably rethink the whole thing. Why not store them on disk in a ready-to-use format?
Arrays are used when the order of items matters. It doesn't seems to really matter here, so why not just keep them as NSDictionaries? If you need to access all keys in a dictionary just call allKeys
EDIT: Like this?
NSString *path = [[NSBundle mainBundle] pathForResource:#"ExerciseDescriptions"
ofType:#"plist"];
NSDictionary *descriptions = [NSDictionary dictionaryWithContentsOfFile:path];
NSMutableArray *exercises = self.exerciseArray;
for (NSInteger i = 0; i < [exercises count]; i++) {
NSMutableDictionary *dict = [[exercises objectAtIndex:i] mutableCopy];
NSString *name = [dict valueForKey:#"exerciseName"];
NSString *desc = [descriptions valueForKey:name];
if (desc) {
[dict setValue:desc forKey:#"exerciseDescription"];
[exercises replaceObjectAtIndex:i withObject:dict];
}
[dict release];
}
I created the following project to address my need to merge two dictionaries created from plists:
https://github.com/bumboarder6/NSDictionary-merge
It works even if you have some duplicate entries between your two dictionaries or arrays and it also recursively merges so you get a merge of your whole plist even when it contains dictionaries of dictionaries of arrays of dictionaries (etc.).
Related
I am studying iPhone development and facing a problem with a reading/writing plist file. I followed an example from a iPhone development book but keep getting an error message when running.
The error message says : 2012-04-26 00:21:09.759 FileHandling[5915:207] -[__NSCFDictionary addObject:]: unrecognized selector sent to instance 0x685ac40
Here is the example code (it seems fine to me...though):
NSString *plistFileName = [[self documentPath] stringByAppendingPathComponent: #"Apps.plist"];
NSLog(#"Where is the file? => %#", plistFileName);
if ([[NSFileManager defaultManager] fileExistsAtPath:plistFileName]) {
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:plistFileName];
for (NSString *category in dict) {
NSLog(#"%#", category);
NSLog(#"=========");
NSArray *titles = [dict valueForKey:category];
for (NSString *title in titles) {
NSLog(#"%#", title);
}
}
} else {
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Apps" ofType: #"plist"];
NSLog(#"%#", plistPath);
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile: plistPath];
NSLog(#"Let's take a look : %#", dict);
NSMutableDictionary *copyOfDict = [dict mutableCopy];
NSLog(#"Let's look at the mutable dictationary : %#", copyOfDict);
NSArray *categoriesArray = [[copyOfDict allKeys] sortedArrayUsingSelector: #selector(compare:)];
for (NSString *cateogry in categoriesArray) {
NSArray *titles = [dict valueForKey: cateogry];
NSMutableArray *mutableTitles = [titles mutableCopy];
[mutableTitles addObject: #"New App Title"];
[copyOfDict setObject: mutableTitles forKey:cateogry];
}
NSString *fileName = [[self documentPath] stringByAppendingPathComponent: #"Apps.plist"];
[copyOfDict writeToFile: fileName atomically:YES];
}
According to the error message, the problem is occurring in the call to addObject: on an __NSCFDictionary. This means that, at runtime, a dictionary received a message to add an object.
However, in this code snippet, addObject: is apparently being sent to an NSMutableArray. This probably means that each object titles you're retrieving from dict in the last for-loop is not an array, but in fact another dictionary, that your code is simply referring to as an array.
Indeed, your code does seem well-formed, so check the well-formedness of your source plist; open it up in a plain text editor. Also, you use a ton of logging, so confirm this way: in the output, dictionaries (including the root entry) are denoted by {curly = braces}, where arrays are denoted by (round parentheses).
I'm trying to convert a NSDictionary to XML. (I was successful in transforming NSDictionary to JSON). But now I need to transform NSDictionary to XML. Is there a built-in serializer in Objective-C like the one for JSON?
int r = arc4random() % 999999999;
//simulate my NSDictionary (to be turned into xml)
NSString *name = [NSString stringWithFormat:#"Posted using iPhone_%d", r];
NSString *stock_no = [NSString stringWithFormat:#"2342_%d", r];
NSString *retail_price = #"12345";
NSArray *keys = [NSArray arrayWithObjects:#"name", #"stock_no", #"retail_price", nil];
NSArray *objects = [NSArray arrayWithObjects:name,stock_no,retail_price, nil];
NSDictionary *theRequestDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
NSDictionary *theFinalRequestDictionary = [NSDictionary dictionaryWithObject:theRequestDictionary forKey:#"product"];
...//other irrelevant code omitted
NSData *theBodyData = [NSPropertyListSerialization dataFromPropertyList:theFinalRequestDictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:nil];
NSPropertyListFormat format;
id XMLed = [NSPropertyListSerialization propertyListFromData:theBodyData
mutabilityOption:NSPropertyListImmutable
format:&format
errorDescription:nil];
NSLog(#"the XMLed is this: %#", [NSString stringWithFormat:#"%#", XMLed]);
The NSLog doesn't print a string in XML format. It prints it like a NSDictionary.
What should I use to serialize my NSDictionary to XML?
propertyListFromData:... returns a "property list object", that is, depending on the contents of the data, an array or a dictionary. The thing that you're actually interested in (the xml) is returned by dataFromPropertyList:... and thus stored in your theBodyData variable.
Try this:
NSLog(#"XML: %#", [[[NSString alloc] initWithData:theBodyData encoding:NSUTF8StringEncoding] autorelease]);
There are many different varieties of XML. If you're not picky about the specific tags, and if the contents of your dictionary is limited to types used in property lists (NSString, NSNumber, NSDate, etc.) you can write your dictionary to a property list in one line:
[myDict writeToFile:somePath atomically:YES];
If you'd prefer to keep the XML in memory instead of writing to a file, use NSPropertyListSerialization as you're doing.
I have a plist containing an array with three elements all of which are dictionaries. These dictionaries contain four items each (latitude, longitude, title, subtitle). I want to loop through each of the elements and get the latitude and longitude (both of which are attributes of the dictionary).
The following code is used.
- (void)loadAnnotations{
//retrieve path of plist file and populate relevant types with its information
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Places1" ofType:#"plist"];
branchList = [[NSArray alloc] initWithContentsOfFile:plistPath];
NSLog(#"hi inside method",branchList.lastObject);
self.branchList =[NSMutableArray array];
//code ok until this
for (NSDictionary *key in branchList)
{ NSLog(#"hi in loop");
PlaceFinderAnnotation *placeAnnotations = [[PlaceFinderAnnotation alloc] init];
//loop through annotations array, creating parking annotations filled with the information found in the plist
CLLocationDegrees latitude = [[key valueForKey:#"latitude"]floatValue];
CLLocationDegrees longitude = [[key valueForKey:#"longitude"]floatValue];
placeAnnotations.coordinate = CLLocationCoordinate2DMake(latitude, longitude);
[self.branchList addObject:placeAnnotations];
[placeAnnotations release]; placeAnnotations= nil;
objectForKey:#"subtitle"]];
}
}
The problem is it doesnt go into the loop. meaning it doesnt print out the log command "hi inside the looppp".
Let's assume this code succeeded (we have to assume because you don't appear to be checking to make sure). Let's also assume (because you don't say) "branchList" is an instance variable in the current class:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Places1" ofType:#"plist"];
branchList = [[NSArray alloc] initWithContentsOfFile:plistPath];
This hopefully leaves you with an array. You could of course eliminate the "hopefully" by ... checking to make sure it leaves you with an array ( if (branchList)... ). Then, since "branchList" seems to be an instance variable, you immediately blow it away by replacing it with an empty array (using an accessor rather than setting it directly as you did above):
self.branchList =[NSMutableArray array];
...so then you try to iterate an empty loop (so the NSLog() statement is never executed).
self.branchList =[NSMutableArray array];
creates an empty array and that is what the for statement is asked to loop through.
Delete that statement.
Perhaps this is what you want:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Places1" ofType:#"plist"];
self.branchList = [NSArray arrayWithContentsOfFile:plistPath];
NSLog(#"hi inside method",branchList.lastObject);
for (NSDictionary *key in self.branchList) {
NSLog(#"hi inside loopppp");
if([branchlist count]){
for (NSDictionary *key in self.branchList) {
NSLog(#"Key item in branchlist %#",key);
}
}else{
NSLog(#"There is no items in branchlist");
}
i have a array with data,i want to load the dictiionary type element with array......
NSArray *thisArray = [[NSArray alloc] initWithContentsOfFile:path];
NSSdictionary *state;
how to load the content with array....
any help appreciated...
The usual pitfall is getting the path right for the file, other than that it is pretty straight forward... if I understand the question correctly
NSString* path = [[NSBundle mainBundle]pathForResource:#"someDataFile" ofType:#"plist"];
NSDictionary *state = [[NSDictionary alloc]initWithContentsOfFile:path];
edited.
Hey, I am trying to write an NSMutableArray to a plist.
The compiler does not show any errors, but it does not write to the plist anyway.
I have tried this on a real device too, not just the Simulator.
Basically, what this code does, is that when you click the accessoryView of a UITableViewCell, it gets the indexPath pressed, edits an NSMutableArray and tries to write that NSMutableArray to a plist. It then reloads the arrays mentioned (from multiple plists) and reloads the data in a UITableView from the arrays.
Code:
NSIndexPath *indexPath = [table indexPathForRowAtPoint:[[[event touchesForView:sender] anyObject] locationInView:table]];
[arrayFav removeObjectAtIndex:[arrayFav indexOfObject:[NSNumber numberWithInt:[[arraySub objectAtIndex:indexPath.row] intValue]]]];
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"arrayFav.plist"];
NSLog(#"%# - %#", rootPath, plistPath);
[arrayFav writeToFile:plistPath atomically:YES];
// Reloads data into the arrays
[self loadDataFromPlists];
// Reloads data in tableView from arrays
[tableFarts reloadData];
CFShow() on the array after removing one of them shows this:
<CFArray 0x6262110 [0x2c810a0]>{type = mutable-small, count = 4, values = (
0 : <CFNumber 0x6502e10 [0x2c810a0]>{value = +3, type = kCFNumberSInt32Type}
1 : <CFNumber 0x6239de0 [0x2c810a0]>{value = +8, type = kCFNumberSInt32Type}
2 : <CFNumber 0x6239dc0 [0x2c810a0]>{value = +10, type = kCFNumberSInt32Type}
3 : <CFNumber 0x6261420 [0x2c810a0]>{value = +40, type = kCFNumberSInt64Type}
DEBUG-INFO: writeToPlist shows YES, I have tried to release all the arrays before filling them up again, setting them to nil, set atomically to NO.
As discussed in the comments below, the actual problem here is that the plist is being read from and written to two different locations. Somewhere in the app, there is code that reads the file into the array similar to this:
NSString *plistFavPath = [[NSBundle mainBundle] pathForResource:#"arrayFav"
ofType:#"plist"];
arrayFav = [[NSMutableArray alloc] initWithContentsOfFile:plistFavPath];
This logic reads the array from the application's bundle, which is a read-only location and part of the distributed app. Later when the edited array is persisted, code similar to this is used:
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES) objectAtIndex:0];
NSString *plistPath = [rootPath
stringByAppendingPathComponent:#"arrayFav.plist"];
NSLog(#"%# - %#", rootPath, plistPath);
[arrayFav writeToFile:plistPath atomically:YES];
The result here is that the updated file gets written to the app's documents directory, but it is never read from there, giving the appearance that the file is not being saved correctly. To correct this, you should change the code that reads the file to use the same path that you are writing to.
If you need to distribute a default version of the plist for use on the initial launch before the array has been edited, you could continue to include a version of the file in your bundle and then add code to your app delegate that check if the file exists in the documents directory and if it is not present, copies the bundle's default version of the file to the proper place.
[yourMutableArray writeToFile:fileName atomically:YES];
This should work. NSMutableArray inherits from NSArray which has a method to write to a plist.
writeToFile:atomically: won't work if your array contains custom objects.
If your array contains custom objects that are not Plist objects (NSArray, NSDictionary, NSString, NSNumber, etc), then you will not be able to use this method. This method only works on Plist objects.
Another option would be to use the NSCoding protocol, and write your objects to disk that way.
Yes
Look at the Property List Programming Guide.
phoneNumbers is a NSMutableArray
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
NSString *error;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"Data.plist"];
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects:
[NSArray arrayWithObjects: personName, phoneNumbers, nil]
forKeys:[NSArray arrayWithObjects: #"Name", #"Phones", nil]];
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];
if(plistData) {
[plistData writeToFile:plistPath atomically:YES];
}
else {
NSLog(error);
[error release];
}
return NSTerminateNow;
}