In my game, I need to instance a certain number of sprites depending on my game's level. That number of sprites is stored in a .plist file. The way my game works is that it selects a random type of enemy for that one particular level. Then, it goes and finds the level number, and then it finds the amount of sprites it need to instance for that level. This is what my plist looks like:
What I need is a way to instance that amount of sprites, no more, no less. I have some ideas on how the code could work, but as I am new to Objective C, I don't know what the code itself would look like.
You can find the number of sprites for a given level like this:
- (int)numberOfSpritesForLevel:(int)level
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"plistFileName" ofType:#"plist"];
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:path];
NSDictionary *levels = [plist objectForKey:#"Mosquito"];
NSString *levelKey = [NSString stringWithFormat:#"L - %d", level]; // Or "L-%d" if there is no space in the keys
return [[levels objectForKey:levelKey] intValue];
}
Then you can create the sprites like this:
int level = 3; // Or whatever value you need.
int numberOfSprites = [self numberOfSpritesForLevel:level];
for (int i = 0; i < numberOfSprites; i++) {
// Create a sprite here
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"YourPlist" ofType:#"plist"];
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:path];
//Get the dictionary
NSDictionary *levels = [plist objectForKey:#"Mosquito"];
for(int i = 0; i < [[levels objectForKey:#"L-1"] intValue]; i++){
//make your sprites
}
I'd do something like the above and substitute in for whichever level you're on.
Related
i have 3 plist files with named level0, level1, level2, all of this plist have the same structure, it consist of number variable and array. I init my app with data from this plist.
+(instancetype)levelWithNum:(int)levelNum; {
NSString* fileName = [NSString stringWithFormat:#"level%i.plist", levelNum];
NSString* levelPath = [[[NSBundle mainBundle]resourcePath]stringByAppendingPathComponent:fileName];
NSDictionary *levelDic = [NSDictionary dictionaryWithContentsOfFile:levelPath];
NSAssert(levelDic, #"level no loaded");
Level *l = [[Level alloc]init];
l.coinsPerLvl = [levelDic[#"coinsPerLvl"] integerValue];
l.words = levelDic[#"words"];
return l;
}
Now i decide to use only one plist and add in it 3 dictionary. How can i read only one dictionary from plist like in example above where i use plist files.
Tanks for any help!
you only need an intermediate variable thats represents the root dictionary and extract the level dictionary from the root dictionary, e.g:
+(instancetype)levelWithNum:(int)levelNum; {
NSString *levelsPlist = [[NSBundle mainBundle] pathForResource:#"levels" ofType:#"plist"];
NSDictionary *rootDict = [NSDictionary dictionaryWithContentsOfFile: levelsPlist];
NSString *levelKey = [NSString stringWithFormat:#"level%i", levelNum];
NSDictionary *levelDic = rootDict[levelKey];
NSAssert(levelDic, #"level no loaded");
Level *l = [[Level alloc]init];
l.coinsPerLvl = [levelDic[#"coinsPerLvl"] integerValue];
l.words = levelDic[#"words"];
return l;
}
Hope it help.
make your plist root as an array (like below). that way you can get levels info easily.
sample code
+(void)levelWithNum:(int)levelNum {
NSString* fileName = #"level.plist";
NSString* levelPath = [[[NSBundle mainBundle]resourcePath]stringByAppendingPathComponent:fileName];
NSArray *levelsArray = [NSArray arrayWithContentsOfFile:levelPath];
NSAssert(levelsArray, #"level no loaded");
Level *l = [[Level alloc]init];
l.coinsPerLvl = [[levelsArray objectAtIndex:levelNum][#"coinsPerLvl"] integerValue];
l.words = [[levelsArray objectAtIndex:levelNum][#"words"] integerValue];
return l;
}
sample plist screen shot
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:
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;
}
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".
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.
Im trying to develop an app that displays a random truth or dare type question, however the user has the ability to turn off truths or dares in option. I have successfully managed to get the app to display a random quote from a plist file from either the truth or dare array also i have managed to program two switch buttons in the user options view controller.
My problem is how would i go about displaying only a truth or dare or both if the user has turned on of the uiswitchs off?
- (IBAction)button:(id)sender
{
if (!self.plistArray)
{
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
if ([[defaults objectForKey:#"truthonoff"] isEqualToString:#"YES"])
{
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray1 = plistDict[#"truth"];
}
if ([[defaults objectForKey:#"dareonoff"] isEqualToString:#"YES"])
{
NSDictionary *plistDict2 = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray2 = plistDict2[#"dare"];
}
self.plistArray = [[plistArray1 arrayByAddingObjectsFromArray:plistArray2] mutableCopy];
}
NSLog(#"%#", plistArray);
//check to see if array is empty and display message
if ([plistArray count] == 0)
{
self.text.text = #"array empty";
}
else
{
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
}
}
That is my attempt however it will not run and i have the feeling it wont ddo the job i need.
Basicly i need it to display only truth if the user has selected that to true or only dare if that is selected or both if both are set to true.
EDIT
sorry the problem with the above code is the plist isnt being loaded and it is scipping straight to if array ==0 {
How do i ensure it loads the array and then checks which arrays to load from the plist file?
Any help is greatly appreciated
This is the code before i tried to add if statements. Im so confussed how best to do this
- (IBAction)shownext:(id)sender {
//load array and check then cycle through this untill array is empty. Array will add two arrays from plist file.
if (!self.plistArray) {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray * plistArray1 = plistDict[#"truth"];
NSDictionary *plistDict2 = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray2 = plistDict2[#"dare"];
self.plistArray = [[plistArray1 arrayByAddingObjectsFromArray:plistArray2] mutableCopy];
}
NSLog(#"%#", plistArray);
//check to see if array is empty and display message
if ([plistArray count] == 0) {
self.text.text = #"array empty";
}
else {
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
}
}
First, if you have a switch for truth and one for dare I hope you have something in place to deal with when the user turns both switches off and doesn't understand why they get nothing (trust me it will happen).
For the rest I'm not sure exactly how you app works but I will take a guess. I'm thinking you have a utility style app with the main UI in one view and then an info button that flips to a second view where the switches are. I'm also guessing that there is a button in the main view that retrieves a truth or dare string. My final assumption, based on your code above, is that when the user changes the state of a switch that writes a user default that you've use a #define to keep out spelling mistakes.
When your view loads you should load both arrays in case the user changes their mind in the middle of using your app and turns on both options or changes from one to the other. Depending on how many entries you have in each of those arrays you might consider creating a combined array as well to simplify things.
When the button is pressed you should then look at the defaults and see if you need to look at both arrays or just one (the below is pseudo code)
if(truth && dare) {
// if you have a combined array pick a random element from it.
// otherwise first randomly pick one of the arrays to pick from.
}
else if (truth) {
// get a random element from the truth array
}
else {
// get a random element from the dare array
}
Also, your current checks of the switch values will always return no unless you are doing extra work in the switch view controller. You should be using [defaults setBool:<UISwitch.isOn> forKey:<#definedKeyHere>] and [defaults boolForKey:<#definedKeyHere>].
It would really help to know what part isn't working. For one thing, it might help to store your flags as NSNumber objects instead of strings (could your string comparison be failing?). Then you could do something like:
if ([[defaults objectForKey:#"truthonoff"] boolValue])
Use a literal to add the actual NSNumber - #YES or #NO.
Consider changing your logic to something like:
VERIFY CODE - doing this freehand:
if (!self.plistArray)
{
self.plistArray = [NSMutableArray array];
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
// why are you loading this twice?
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([[defaults valueForKey:#"truthonoff"] boolValue])
{
[self.plistArray addObject:plistDict[#"truth"]];
}
if ([[defaults valueForKey:#"dareonoff"] boolValue])
{
[self.plistArray addObject:plistDict[#"dare"]];
}
}
I am assuming that your code to load the Plists is working. Verify all your keys match what's in the Plist. Set a breakpoint and verify.
Calling a method on a nil object is a no-op in Objective C. So, it'll happily ignore calls to nil objects without telling you. Verify you have what you think you have.
Also, here:
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
Consider using arc4random_uniform(self.plistArray.count) as it avoids modulo bias in the generator.
Now, this just gives you say 0 or 1 if you have two elements. Are you sure the two dictionary keys, "truth" and "dare" actually point to arrays?
Ok, everything working to this point. Now, you have an array of ARRAYS! So you need to randomly pick a question array, and THEN randomly pick a question.
Something like:
//get random array
int randArrayIndex = arc4random_uniform(self.plistArray.count);
NSArray* questionArray = self.plistArray[randArrayIndex];
//get random question
int randQuestionIndex = arc4random_uniform([questionArray count]);
NSString* randomQuestion = questionArray[randQuestionIndex];
self.text.text = randomQuestion;
// remove question
[questionArray removeObjectAtIndex:randQuestionIndex];
Something like that anyway. Of course, assuming you are storing NSStrings in those Plist arrays.
Trying to read a plist and change my font color depending on the option that was selected in the following settings bundle.
The following is how I am trying to accomplish it:
NSString *path = #"/var/mobile/Library/Preferences/NCNotes.plist";
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
fontSize = [[dict objectForKey:#"slideSwitched"] floatValue];
if ([[dict objectForKey:#"noteColor"] valueForKey:#"Purple"]) {
noteView.textColor = [UIColor purpleColor];
} else {
noteView.textColor = [UIColor blackColor];
}
Any ideas why this is why my app is crashing? How do I read the values and change the color depending on what was selected?
It appears that the top level of your plist is an array, not a dictionary, because at the top it says "Item 1" where all of your content is within that. So you have a dictionary within an array. So you can change your code like this:
NSString *path = #"/var/mobile/Library/Preferences/NCNotes.plist";
NSArray *array = [[NSArray alloc] initWithContentsOfFile:path];
NSDictionary *dict = array[0];
You could also change the structure of your plist so that you have a dictionary as the root instead of an array.
Also, keys are supposed to be on the left-hand side and their values on the right-hand side, so I don't see a key "noteColor". You have a key "key" with a value "noteColor", so you'll need to make that correction. I'm also not seeing a "slideSwitched" key, though it might just be outside the bounds of your screenshot.
Also the following won't work:
[[dict objectForKey:#"noteColor"] valueForKey:#"Purple"]
Whatever you get from [dict objectForKey:#"noteColor"] isn't going to be a dictionary, so calling valueForKey: on that isn't going to give you what you want.
simply you should do this with document directory
NSString *contentPath=[[NSBundle mainBundle] pathForResource:#"PLIST_FILE_NAME" ofType:#"plist"];
NSDictionary *dictionary=[NSDictionary dictionaryWithContentsOfFile:contentPath];
write your logic after this, wait a minute , its seems like you dont have a key "noteColor" also. check your plist
Here is some example code documented up the wazoo. Hopefully it will help you understand how these plists and dictionaries work. Everything will be based on your plist file (which could definitely be improved upon, but that's up to you as I don't know your specific situation).
Your question is "How do I find color based on user selection?" I will assume you get the user selection as an int. Something like "User selected 7".
//Load your plist dictionary
NSString *path = #"/var/mobile/Library/Preferences/NCNotes.plist";
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
//Get the array of validValues and the array of validTitles
NSArray *valuesArray = [dict objectForKey:#"validValues"];
NSArray *titlesArray = [dict objectForKey:#"validTitles"];
//Now get the user selected index from the validValues array
int arraySelection = -1;
for(int i = 0; i < [valuesArray count]; i++)
{
NSNumber *number = [valuesArray objectAtIndex:i];
if([number intValue] == userSelectedInput)
{
arraySelection = i;
break;
}
}
if(arraySelection == -1)
{
//Not found in array
return;
}
//Now with that index get the title of the object that the user selected
NSString *userSelectedTitle = [titlesArray objectAtIndex:arraySelection];
//Now do your checking on what the user selected based on that:
if([userSelectedTitle isEqualToString:#"Purple"])
...
You could boil this down quite a bit. Currently your validValues array is completely useless. If it were out of order or missing numbers then it would be needed, but straight counting can be achieved by the validTitles array.
I have this code from which i am checking songs that are present in my app and storing the number of songs in an NSMUTABLEArray but there is something wrong it always shows null
for (int i = 1; i < 100; ++i) {
// Load the plist
NSString *plist = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"%d",i] ofType:#"mp3"];
if (nil == plist) {
NSLog(#"not found (%i)", i);
continue;
}
NSLog(#"Found Songs (%i)",i);
[MYNSMUTABLEArray addObject:[NSNumber numberWithInt:i]];
NSLog(#"%#",MYNSMUTABLEArray);
}
the i variable is working absolutely fine
You need to create your mutable array, so that an object exists to store the data. I presume that the code at the moment is sending the addObject: message to nil.
I'd strongly suggest a better variable name than MYNSMUTABLEArray, so here is my code snippit that should go before the loop.
NSMutableArray* myMutableArray = [NSMutableArray alloc] init];
Then where you are adding the object, you'd use:
[myMutableArray addObject:#(i)];
Bootnote: A little tip, you can use # literals to automatically box your primitive int value into an NSNumber object. This is used in the addObject: example, and is in the form #(int).