How to pass these parameters into an NSString nsuserdefault - ios

I have 14 nsuserdefualt save keys and instead of adding all 14 of them I created a for loop to handle this. However I am getting an error that says too many arguments. I am probably having a brain fart and forgot something. Any tips or suggestions will be appreciated.
Edit: I am trying to read the saved data.
for (int n=0; n==14; n++ ) {
NSString *emailBody=[NSString stringWithFormat:#"Enhancers: %#",
[[NSUserDefaults standardUserDefaults]
stringForKey:#"Enhancer%i",n]];
}

You had an extra argument in your format string specifically "n" that should have been placed in a different format for stringForKey:. Something like this should clear things up:
for (int n=0; n==14; n++ ) {
NSString *stringFromDefaults = [[NSUserDefaults standardUserDefaults] stringForKey:[NSString stringWithFormat:#"%d",n]];
NSString *emailBody=[NSString stringWithFormat:#"Enhancers: %#",stringFromDefaults];
}

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.

Load array and display based on state of uiswitch

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.

NSUserDefaults. Retrieving incorrect values

I am saving few dictionaries in NSUserDefaults this way:
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"event_states"] == nil)
{
NSDictionary *dict0 = [NSDictionary dictionaryWithObjectsAndKeys:
#"923381DC-3DCB-46CA-A6CF-C58A7AF33B89",#"guid",
#"В планах",#"name", //this string will returned corrupted
[NSNumber numberWithInt:12632256],#"color",
[NSNumber numberWithInt:0],#"isclosed",
[NSNumber numberWithInt:0],#"isfinish",
nil];
// and few more dictionaries same way
....
[[NSUserDefaults standardUserDefaults] setObject:#[dict0,dict1,dict2,dict3] forKey:#"event_states"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
NSArray *event_states = [[NSUserDefaults standardUserDefaults] objectForKey:#"event_states"];
When I am retrieving this data I am logging it and have a strange behavior. In some cases I am retrieving a correct value as this:
name = "\U0412 \U043f\U043b\U0430\U043d\U0430\U0445"
but in some cases as this:
name = "\U0412\U044b\U043f\U043e\U043b\U043d\U0435\U043d(\U043e)
and this last part in parenthesis is an extra symbol and I can't understand why it appears and what is dependence on when it appears and when it's not. What can be wrong with this?
I tried to convert your unicode to readable string and paste it in google translate
\U0412 \U043f\U043b\U0430\U043d\U0430\U0445 => В планах => The plans
\U0412\U044b\U043f\U043e\U043b\U043d\U0435\U043d(\U043e) => Выполнен(о) => Complete ?
So I think this happened because your code not the API.

iOS - how to handle string concatenation when a value can be nil?

I am doing something like this:
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *var1 = [standardUserDefaults objectForKey:#"obj1"];
NSString *var2 = [standardUserDefaults objectForKey:#"obj2"];
EmailUtil *email_obj = [[EmailUtil alloc] initWithSubject:#"Some subject" body:[[[#"This is var1: " stringByAppendingString: var1] stringByAppendingString:#" and var2: "] stringByAppendingString: var2]];
[email_obj send];
Where EmailUtil is just my own utility class which sends the email. This code works fine when the strings var1 and var2 are not nil; however, if they are nil the program will crash. What would be an elegant solution or good practice to follow to ensure the email is sent without a problem, regardless of what the values are?
Thanks!
Before your EmailUtil line, just add:
if(var1==nil) var1 = #"";
if(var2==nil) var2 = #"";
That should solve the crash at least.
Another and probably better way to do it would be to use NSString's stringWithFormat.
Instead of:
[[[#"This is var1: " stringByAppendingString: var1] stringByAppendingString:#" and var2: "] stringByAppendingString: var2]
Do:
[NSString stringWithFormat:#"This is var1: %# and var2: %#", var1,var2];
I believe it will say (null) for the nil parameters (didn't test this). This is probably cleaner than what I had, and your original code too.
Reference: String Format
I'd be tempted to create a NSUserDefaults category with a method that returned a guaranteed string, substituting #"" for nil or perhaps a default value that you provide. It might be overkill if your only concern (ever!) is two variables but it might also be handy for re-use.

Sharing a NSString between two views

I need to share a string between two views in my application. When the user ends the game, the score is converted into a string. I then need to transfer that string into a different view controller where I display the score. I have a label set up and all but the view is not recognizing the string even though I am importing the header file from which the string is created. Any help would be great, thank.
This is my view controller where the string is created
NSString *scoreString = [NSString stringWithFormat:#"%d", score];
And this is where I try to display the string in a different view controller
- (void)viewDidLoad {
self.scoreString = score.text;
[super viewDidLoad];
}
For scores and similar data, you may want to use NSUserDefaults. These can be accessed at any time from any UIViewController. For example, you can implement methods similar to these to save and retrieve the data:
-(void)saveToUserDefaults:(NSString*)myString
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setObject:myString forKey:#"Score"];
[standardUserDefaults synchronize];
}
}
-(NSString*)retrieveFromUserDefaults
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *val = nil;
if (standardUserDefaults)
val = [standardUserDefaults objectForKey:#"Score"];
return val;
}
NSUserDefaults also handles ints, BOOLs, NSArrays, etc. Check out the documentation or google around for examples.
You could use delegates or NSUserDefaults
Been asked similiar questions before:
Passing variables to different view controllers
How do I pass variables between view controllers?
EDIT
[[NSUserDefaults standardUserDefaults] setObject:#"STRING HERE" forKey:#"MyKey"];
and retrieve
[[NSUserDefaults standardUserDefaults] objectForKey:#"MyKey"];

Resources