iOS: Read data from .plist in dictionary - ios

I would like to retrieve data from a plist in my .app directory.
I can not figure out how to get sub-dictionary data. For instance, I would like to get the MemoriesDictionary/Memory1/Event1/EventName value.
I am able to get the MemoryCount value into iMemCount just fine with:
int iMemCount;
//Do file searching/getting for plist
NSString *plistDirectory = [[NSBundle mainBundle] pathForResource:#"memoryDetails" ofType:#"plist"];
NSLog(#"array: %#",plistDirectory);
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath: plistDirectory]) //4
{
NSLog(#"exists");
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile: plistDirectory];
iMemCount = [[savedStock objectForKey:#"MemoryCount"] intValue];
} else {
NSLog(#"Does Not Exist");
iMemCount = 0;
}

NSString *string = savedStock[#"MemoriesDictionary"][#"Memory1"][#"Event1"][#"EventName"];
Edit
You could also rewrite:
int iMemCount;
iMemCount = [[savedStock objectForKey:#"MemoryCount"] intValue];
as
NSInteger iMemCount;
iMemCount = [savedStock[#"MemoryCount"] integerValue];
You really should be taking into account the possibility of your iOS code running on a 64-bit processor and use the appropriate platform safe types (NSInteger, CGFloat, CFIndex, etc)

Note also that using an NSMutableDictionary here may not do what you expect: the top-level dictionary will be mutable but all the sub-objects (arrays, dictionaries) will be immutable and throw an exception if you try to access them.
In general I’d caution against doing lookups several levels deep in dictionaries, because it’s usually a sign that you’re doing something the hard way. The pattern I like to follow is create classes that can read and write themselves to dictionaries, and then when I read in a file create instances that can be queried directly.
Dealing with a bunch of mutable dictionaries with a bunch of string keys is a recipe for heartache and disaster. You lose compile-time type checking and compile-time variable name checking and readability.
Also, I don’t know if this is a contrived example file, but I wouldn’t write the count to the file explicitly—just calculate it as needed. Duplicating data leads to data being out of sync. And it seems like MemoriesDictionary really wants to be an array, if the names of the memories are inside the sub-dictionaries, and the keys are used to keep the memories in order.

Related

Saving Successfully, but not Visibly Editing plist

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.

How do I access random values from an array nested in a dictionary from plist file?

Basically I need to get a random letter and the points associated with that letter from a plist.
I'm honestly not sure I have my plist set up in the best possible way to do this, I've never worked with plist before:
As soon as I get this working I'm going to be adding C-Z into the plist with each letters associated points. I'm just created a basic word tile game to try and learn sprite kit.
So far I've been able to access the plist file but have not had any luck getting a random Letter and it's Points.I'm getting all kinds of errors with everything I try.
Here's my code so far to access the plist:
NSString* fileName = #"letters.plist";
NSString* filepath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
NSDictionary* lettersPlist = [NSDictionary dictionaryWithContentsOfFile:filepath];
NSDictionary* letters = lettersPlist[#"letters"];
So how do I get a random letter and it's points from this plist? And is there a better way to do this?
Just get a random number between 0 and the number of items in your letters array, and take that info out. For example, continuing your code:
u_int32_t bound = (u_int32_t)[letters count];
NSDictionary* randomLetterInfo = letters[arc4random_uniform(bound)];
NSString* randomLetter = randomLetterInfo[#"Letter"];
NSString* points = randomLetterInfo[#"Points"]; // points are a string in your plist

Extracting unknown data from plist

I'm writing an iOS loader that loads data from a plist intending to send vertex data, etc. to the GPU via OpenGL. I can easily extract objects of standard types, like strings, integers, etc.
Where I get stumped is when I encounter what appears to be raw data as a dictionary object. The plist is a native file saved by my 3D modeling software, of which I'm not the author, so I don't know how the data was written into this object.
Some things I DO know about the object, it's likely an array of floats, each vertex needs a float value for X, Y, and Z, and there are 26 vertices in the example below.
Here's the actual data object in the plist file:
<key>vertex</key>
<data>
AAAAAL8AAAAAAAAAAAAAAAAAAAC/AAAAPwAAAAAAAAA+gAAAvwAA
AD7ds9cAAAAAPt2z2L8AAAA+f///AAAAAD8AAAC/AAAAsru9LgAA
AAA+3bPXvwAAAL6AAAEAAAAAPoAAAb8AAAC+3bPXAAAAALM7vS6/
AAAAvwAAAAAAAAC+gAADvwAAAL7ds9UAAAAAvt2z2L8AAAC+f//9
AAAAAL8AAAC/AAAAMczeLgAAAAC+3bPYvwAAAD5///0AAAAAvn//
+L8AAAA+3bPaAAAAAD6AAAA/AAAAPt2z1wAAAAAAAAAAPwAAAD8A
AAAAAAAAAAAAAD8AAAAAAAAAAAAAAD7ds9g/AAAAPn///wAAAAA/
AAAAPwAAALK7vS4AAAAAPt2z1z8AAAC+gAABAAAAAD6AAAE/AAAA
vt2z1wAAAACzO70uPwAAAL8AAAAAAAAAvoAAAz8AAAC+3bPVAAAA
AL7ds9g/AAAAvn///QAAAAC/AAAAPwAAADHM3i4AAAAAvt2z2D8A
AAA+f//9AAAAAL5///g/AAAAPt2z2gAAAAA=
</data>
Any ideas about how to read this? Here's where I am:
// get plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"Cylinder" ofType:#"jas"];
NSDictionary *cheetahFile = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *objectArray = [cheetahFile objectForKey:#"Objects"];
NSDictionary *model = [objectArray objectAtIndex:1];
//get vertex count
GLshort vertCount = [[model valueForKey:#"vertexcount"] intValue];
//All good so far...but...
//get vertex data?... this doesn't work:
NSMutableArray *vertArray = [NSMutableArray arrayWithObject:[model objectForKey:#"vertex"]];
P.S. Sorry in advance if I'm making a rookie mistake. I'm a designer by profession, not a programmer. So talk slow using soothing tones while I eat my crayons. :)
The <data> part is an encoded NSData object. You can do this:
NSData *vertexData = model[#"vertex"];
What you do with that data is a whole other discussion.

Fast Enumeration error?

Getting a warning saying that:
Collection expression type 'NSString *' may not respond to 'countByEnumeratingWithState:objects:count'
when trying to run the following code:
NSString *proper = [NSString stringWithContentsOfFile:#"usr/share/dict/propernames" encoding:NSUTF8StringEncoding error:NULL];
for (NSString *i in proper){
NSLog(#"Item>%#", i);
}
When I run the program I don't get any output from the NSLog statement. Anyone run into this?
The compiler warning is trying to tell you that you cannot iterate over an NSString using a for ... in ... loop. It is further trying to say than the reason for this is that an NSString is not a valid "Collection" type. Your valid "Collection" types are things like NSArray, NSSet, and NSDictionary.
So if your file is supposed to contain structured data, you should parse it into something more useful than a flat NSString. Something like this may give a better result:
NSString* temp = [NSString stringWithContentsOfFile:#"usr/share/dict/propernames" encoding:NSUTF8StringEncoding error:NULL];
NSArray* proper = [temp componentsSeparatedByString:#"\n"];
for (NSString* i in proper){
NSLog(#"Item>%#", i);
}
That will print each line in the file. This assumes, of course, that your input file has one entry per line. If it is structured some other way, then you will have to parse it differently.
After you load the file, split it into lines with componentsSeparatedByString: .
NSArray *lines = [proper componentsSeparatedByString:#"\n"]; // Use the correct line ending character here
for (NSString *i in lines) {
NSLog(#"item> %#", i);
}
This is a clear mistake u have here.
NSString* is a string object and is not a collection of characters as the string object known in C++ which is a character array.
for-in Loop need a collection to iterate within. like NSArray or NSSet.

iOS5 using ARC: Implicit conversion of int to NSDictionary

i´m using ARC to update my old project.
I have a "Filehelper" class in which i use c-functions e.g. for methods i need in almost every projects. (eg.load plist, etc..)
ViewController
NSDictionary* dictionary = getDictionaryFromPlistWithName(#"TestFile", #"plist");
Filehelper.m
#pragma Returns content of plist as NSDictionary
NSDictionary* getDictionaryFromPlistWithName(NSString* fileName, NSString* pathExtension) {
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:pathExtension];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:path];
if(fileExists){
NSDictionary* dictionary = [NSDictionary dictionaryWithContentsOfFile:path];
return dictionary;
}
else{
[NSException raise:#"File not found" format:#"path %#", path];
return nil;
}
}
I get this error:
*error: Automatic Reference Counting Issue: Implicit conversion of 'int' to 'NSDictionary ' is disallowed with ARC
Any ideas how to fix it to use it with iOS 5?
I´ve read something that i need to use (__bridge NSDictionary*), but that didn´t help.
PS.
What´s your workflow for classes which you already need? Use C-functions, too?=
What´s the best way?
The most likely problem here is that you forgot to #include your header file. I bet there are further warnings that you're ignoring, particularly one that says something like "unknown signature for getDictionaryFromPlistWithName, assuming it returns int". Read through your warnings and never ignore them (I strongly recommend -Werror for all ObjC projects).

Resources