0

I've already looked at Parse Plist (NSString) into NSDictionary and deemed it to be not a duplicate, as that question and its answer do not address my concerns.


I have a .plist file in the file system structured like this:

Root > My App > Side Panel > Items > Price

The source code of this .plist file looks like this:

{
    "My App" = {
        "Side Panel" = {
            Items = {
                Price = "#123ABC";
            };
        };
    };
}

I know how to get an item in the Root like this:

[[NSBundle mainBundle] pathForResource:@"filename" ofType:@"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSString value = [dict objectForKey:@"key"]);

But what if the structure is like mine, with tiered dictionaries? How do I get the value of Price?

I would like to do this all in one method, ideally like this:

Calling

NSString *hexString = [self getColorForKey:@"My App.Side Panel.Items.Price"];

Definition

- (NSString *) getColorForKey: (NSString *)key
{
    NSArray *path = [key componentsSeparatedByString:@"."];
    NSDictionary *colors = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Colors" ofType:@"plist"]];
    NSString *color = @"#FFFFFF"; // white is our backup

    // What do I put here to get the color?

    return color;
}
Community
  • 1
  • 1
Ky -
  • 30,724
  • 51
  • 192
  • 308
  • possible duplicate of [Parse Plist (NSString) into NSDictionary](http://stackoverflow.com/questions/1072308/parse-plist-nsstring-into-nsdictionary) – Louis Tur Apr 20 '15 at 16:40
  • @LouisTur That deals with one that's parsed from a String in memory, and I don't see how it or its answers applies to my issue. – Ky - Apr 20 '15 at 16:45
  • @LouisTur yeah, that answer doesn't help me at all. Could you elaborate on how you think it's related? Perhaps knowing more about your thought process would help. – Ky - Apr 20 '15 at 16:55
  • 1
    Are you asking how to get to a subnode in an `NSDictionary`? It's conceptually the same as getting the root node, `NSString *price = dict[@"Root"][@"MyApp"][@"Side Panel"][@"Items"][@"Price"]`. Using KVO is also a possibility. Traversing a dictionary is a fundamental concept, regardless of your data source, I would of done a quick search on the topic before posting. But this may help: http://appventure.me/2011/12/07/fast-nsdictionary-traversal-in-objective-c/ – Louis Tur Apr 20 '15 at 17:05
  • @LouisTur I've added an example case of how I want to do this. Does this help you understand my problem? – Ky - Apr 20 '15 at 17:14

2 Answers2

0

Here's the solution that worked for me:

+ (NSString*) getHexColorForKey:(NSString*)key
{
    NSArray *path = [key componentsSeparatedByString:@"."];
    NSDictionary *colors = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Colors" ofType:@"plist"]];
    NSString *color = @"#FFFFFF";
    for (NSString *location in path) {
        NSObject *subdict = colors[location];
        if ([subdict isKindOfClass:[NSString class]])
        {
            color = (NSString*)subdict;
            break;
        }
        else if ([subdict isKindOfClass:[NSDictionary class]])
        {
            colors = (NSDictionary*)subdict; // if it's a dictinoary, our color may be inside it
        }
        else
        {
            [SilverLog level:SilverLogLevelError message:@"Unexpected type of dictinoary entry: %@", [subdict class]];
            return color;
        }
    }
    return color;
}

where key is an NSString that matches /^[^.]+(\.[^.]+)*$/, meaning it looks like my targeted @"My App.Side Panel.Items.Price".

Ky -
  • 30,724
  • 51
  • 192
  • 308
0

Yes I understand what you're looking to accomplish; thank you for the clarification. I will however add that the resources and advice I have written do provide the necessary information resolve your problem.

That said, the following gets your dictionary:

NSURL *plistURL = [[NSBundle mainBundle] URLForResource:@"Info" withExtension:@"plist"];
NSData *plistData = [NSData dataWithContentsOfURL:plistURL];
NSDictionary *tieredPlistData = [NSPropertyListSerialization propertyListWithData:plistData 
                                                                          options:kCFPropertyListImmutable 
                                                                           format:NULL 
                                                                            error:nil];

Then, if we're interested in the information contained in Items

NSDictionary *allItemsDictionary = tieredPlistData[@"My App"][@"Side Panel"][@"Items"];

Assuming that Items will contain a number of objects, you could use

NSArray *keys = [allItems allKeys];
for(NSString *key in keys){
    NSString *colorValue = allItemsDictionary[key];
    // do something with said color value and key
}

Or, if there is a single value you need, then just reference that key

NSString *colorForPriceText = allItemsDictionary[@"Price"];

But a few tips:

  • It's generally considered a better idea to keep frequently accessed values in code instead of a plist/file that is loaded at runtime.
  • That said, you wouldn't put your call to load from NSBundle in the same method you would use to query a specific value. In your example, every time you need a color, you end up re-accessing NSBundle and pile on unneeded memory allocations. One method would load the plist into an iVar NSDictionary and then that NSDictionary would be used separately by another method.
Louis Tur
  • 1,303
  • 10
  • 16
  • Does that work if there are two values called price? Say, `My App.Side Panel.Items.Price` and `My App.Printout.Items.Price`? – Ky - Apr 20 '15 at 22:04
  • Well, yes and no.. it doesn't matter that they are the same key because the path to get to them are different -- one would be `plist[@"My App"][@"Side Panel"]...` and the other would be `plist[@"My App"][@"Printout"]...`. But the code currently cannot get to the second one because it hard codes the base path as `allItemsDictionary = tieredPlistData[@"My App"][@"Side Panel"][@"Items"]` – Louis Tur Apr 20 '15 at 22:52
  • I mean your second solution: `NSString *colorForPriceText = allItemsDictionary[@"Price"];` – Ky - Apr 21 '15 at 13:13