Disclaimer: major noob
I'm writing an arithmetic flash card app as a learning project. I have a UITabViewController with the bottom tab bar that toggles between a few different views. Everything works okay until I try to set NSUserDefault boolean values in the Settings view controller and try to read those values in the Flashcards view controller.
The settings view has a switch to enable/disable each operator (addition, subtraction, etc) and the flashcard view should randomly present a flash card if that type of operation was enabled.
I believe that my mistake is that I don't understand the key concept of data encapsulation, objects, etc. I'd appreciate any help.
Here is the Settings view controller. I'm not even sure how to put the code into this forum so this might be a laughable moment... here goes:
// settings_flashcards.h
#import <UIKit/UIKit.h>
#interface settings_flashcards : UIViewController {
UISwitch *additionSwitch;
UISwitch *subtractionSwitch;
UISwitch *multiplicationSwitch;
UISwitch *divisionSwitch;
}
#property (nonatomic,retain) IBOutlet UISwitch *additionSwitch;
#property (nonatomic,retain) IBOutlet UISwitch *subtractionSwitch;
#property (nonatomic,retain) IBOutlet UISwitch *multiplicationSwitch;
#property (nonatomic,retain) IBOutlet UISwitch *divisionSwitch;
#end
and...
/ settings_flashcards.m
#import "settings_flashcards.h"
#implementation settings_flashcards
#synthesize additionSwitch;
#synthesize subtractionSwitch;
#synthesize multiplicationSwitch;
#synthesize divisionSwitch;
- (void)viewDidLoad {
[additionSwitch addTarget:self action:#selector(additionSwitchFlipped) forControlEvents:UIControlEventValueChanged];
[subtractionSwitch addTarget:self action:#selector(subtractionSwitchFlipped) forControlEvents:UIControlEventValueChanged];
[multiplicationSwitch addTarget:self action:#selector(multiplicationSwitchFlipped) forControlEvents:UIControlEventValueChanged];
[divisionSwitch addTarget:self action:#selector(divisionSwitchFlipped) forControlEvents:UIControlEventValueChanged];
[super viewDidLoad];
}
-(void) additionSwitchFlipped {
if (additionSwitch.on) {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"additionKey"];
}else {
[[NSUserDefaults standardUserDefaults] setBool:FALSE forKey:#"additionKey"];
}
}
-(void) subtractionSwitchFlipped {
if (subtractionSwitch.on) {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"subtractionKey"];
}else {
[[NSUserDefaults standardUserDefaults] setBool:FALSE forKey:#"subtractionKey"];
}
}
-(void) multiplicationSwitchFlipped {
if (multiplicationSwitch.on) {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"multiplicationKey"];
}else {
[[NSUserDefaults standardUserDefaults] setBool:FALSE forKey:#"multiplicationKey"];
}
}
-(void) divisionSwitchFlipped {
if (divisionSwitch.on) {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"divisionKey"];
}else {
[[NSUserDefaults standardUserDefaults] setBool:FALSE forKey:#"divisionKey"];
}
}
Here is the Flashcards view...
// flashcardsViewController.h
#import <UIKit/UIKit.h>
#interface flashcardsViewController : UIViewController <UIActionSheetDelegate>{
UILabel *firstNumberLabel;
UILabel *secondNumberLabel;
UILabel *answerNumberLabel;
UILabel *operatorLabel;
BOOL additionIsEnabled;
BOOL subtractionIsEnabled;
BOOL multiplicationIsEnabled;
BOOL divisionIsEnabled;
}
#property (nonatomic,retain) IBOutlet UILabel *firstNumberLabel;
#property (nonatomic,retain) IBOutlet UILabel *secondNumberLabel;
#property (nonatomic,retain) IBOutlet UILabel *answerNumberLabel;
#property (nonatomic,retain) IBOutlet UILabel *operatorLabel;
-(void) buttonClicked:(id)sender;
#end
and...
// flashcardsViewController.m
#import "flashcardsViewController.h"
#implementation flashcardsViewController
#synthesize firstNumberLabel;
#synthesize secondNumberLabel;
#synthesize answerNumberLabel;
#synthesize operatorLabel;
- (void)viewDidLoad {
srand(time(0)); //seed random
//the following should assign the keys if they don't exist
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"additionKey"]){
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"additionKey"];
}
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"subtractionKey"]) {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"subtractionKey"];
}
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"multiplicationKey"]) {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"multiplicationKey"];
}
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"divisionKey"]) {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"divisionKey"];
}
//the following should assign each BOOL variable based on the key
additionIsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:#"additionKey"];
subtractionIsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:#"subtractionKey"];
multiplicationIsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:#"multiplicationKey"];
divisionIsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:#"divisionKey"];
[super viewDidLoad];
}
-(void) buttonClicked:(id)sender{
int a = rand() % 4;// random number generator (number to enter loop)
if ( additionIsEnabled || subtractionIsEnabled || multiplicationIsEnabled || divisionIsEnabled) {
while (a < 5) {
switch (a) {
case 0:
if (additionIsEnabled == TRUE){
int x = rand() % 11 + 1;
int y = rand() %11 + 1;
firstNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",x];
secondNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",y];
answerNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",(x+y)];
operatorLabel.text = [[NSString alloc]initWithFormat: #"+"];
a = 5;
}
else a++;
break;
case 1:
if (subtractionIsEnabled == TRUE){
int x = rand() % 19 + 1;
int y = rand() %11 + 1;
firstNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",x];
secondNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",y];
answerNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",(x-y) ];
operatorLabel.text = [[NSString alloc]initWithFormat: #"-"];
a = 5;
}
else a++;
break;
case 2:
if (multiplicationIsEnabled == TRUE){
int x = rand() % 11 + 1;
int y = rand() %11 + 1;
firstNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",x];
secondNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",y];
answerNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",(x*y)];
operatorLabel.text = [[NSString alloc]initWithFormat: #"×"];
a = 5;
}
else a++;
break;
case 3:
if (divisionIsEnabled == TRUE){
int x = rand() % 11 + 1;
int y = rand() % 11 + 1;
firstNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",(x*y)];
secondNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",y];
answerNumberLabel.text = [[NSString alloc]initWithFormat: #"%i",x];
operatorLabel.text = [[NSString alloc]initWithFormat: #"÷"];
a = 5;
}
else a = 0;
break;
default:
break;
}
}
}
else
{
UIAlertView *noOperatorSelectedAlert = [[UIAlertView alloc]initWithTitle:#"You have not set any operations."
message:#"Return to the settings menu and decide which operations you wish to perform. (addition, subtraction, etc.)"
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[noOperatorSelectedAlert show];
[noOperatorSelectedAlert release];
}
}
There are a few things to do here. Firstly you want a better way of saying what the default state is before the user has made any explicit decisions. Next you want to know when you should refresh your in-app state from user preferences.
Defaults Initialization
The first item's solution is to put the default values into the registration domain for your user preferences. This is something you'll do during application initialization, rather than having your views individually check preferences and update them at initialization time. The preferences system looks in quite a few places for data, and the first place it looks is in the in-memory-only registration domain. This is where you'll put the default (i.e. no value specified means 'x') values for each of your user preferences.
The API you'll use for this is -[NSUserDefaults registerDefaults:], which takes an NSDictionary of values. To set your default values of YES (in Objective-C the BOOL type uses YES and NO rather than TRUE and FALSE) you'll use something like this, commonly executed in a +initialize method for your application's main class:
+ (void) initialize
{
// in any +initialize, make sure it's being called on your class
// +initialize is different from all other methods in this respect
if ( [self isKindOfClass: [MyApplicationDelegate class]] == NO )
return; // being called on a superclass, don't do my stuff
// set up default values for certain preferences
NSMutableDictionary * defaults = [NSMutableDictionary new];
[defaults setObject: [NSNumber numberWithBool: YES] forKey: #"additionKey"];
[defaults setObject: [NSNumber numberWithBool: YES] forKey: #"subtractionKey"];
[defaults setObject: [NSNumber numberWithBool: YES] forKey: #"multiplicationKey"];
[defaults setObject: [NSNumber numberWithBool: YES] forKey: #"divisionKey"];
// set this as the registration domain
[[NSUserDefaults standardUserDefaults] registerDefaults: defaults];
[defaults release];
}
Usually you'll put everything into one method like this, so if you have other parts of the application which expect a default non-zero value for any preference, you should add those to this group and place it into your application delegate's +initialize method.
Now, when your other classes use [[NSUserDefault standardUserDefaults] boolForKey: #"additionKey"] and there is nothing saved to the preference files for that key, they will get the value supplied above.
Refreshing Cached Values
So, you have a view where the user is able to change these preferences. Your next job is to make the view above that update its member variables using the new preferences. For this we can use either delegation or notification. In this case, I'll go with notifications for to reasons:
You're using NSUserDefaults, which can theoretically change in many different places. Delegation would only inform you of changes made by one object.
NSUserDefaults already implements a handy notification which you can watch.
So, in your flashcardsViewController you'll have something like these few methods:
- (void) updateFromPreferences
{
// fetch current values from user defaults into your member variables
additionIsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey: #"additionKey"];
// etc...
}
- (void) viewWillLoad
{
// load variables from user defaults
[self updateFromPreferences];
// find out when the preferences have been changed
// this will cause -updateFromPreferences to be called
// whenever something changes preferences, inside the app or outside
[[NSNotificationCenter defaultNotificationCenter] addObserver: self
selector: #selector(updateFromPreferences)
name: NSUserDefaultsDidChangeNotification
object: nil];
}
- (void) viewDidUnload
{
// always remove notification observers.
[[NSNotificationCenter defaultNotificationCenter] removeObserver: self
name: NSUserDefaultsDidChangeNotification
object: nil];
}
- (void) dealloc
{
// add this to your existing dealloc routine
[[NSNotificationCenter defaultNotificationCenter] removeObserver: self
name: NSUserDefaultsDidChangeNotification
object: nil];
}
Summary
Taken together, these two should give you everything you need to make this work.
In addition to the "always YES" problem pointed out by Chiefly Izzy, your -buttonClicked: method does not read new values from NSUserDefaults. These values are read (once) in [flashcardsViewController viewDidLoad]. If they are changed in settings, the change will not be detected in flashCardsViewController until the next time it is loaded (probably the next time the application is launched).
The simplest approach would be to move your code for reading the IsEnabled BOOL values into the -buttonClicked method, like so:
-(void) buttonClicked:(id)sender {
// the following should assign each BOOL variable based on the key
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
additionIsEnabled = [defaults boolForKey:#"additionKey"];
subtractionIsEnabled = [defaults boolForKey:#"subtractionKey"];
multiplicationIsEnabled = [defaults boolForKey:#"multiplicationKey"];
divisionIsEnabled = [defaults boolForKey:#"divisionKey"];
int a = rand() % 4;
if ( additionIsEnabled || subtractionIsEnabled ...
This code ...
//the following should assign the keys if they don't exist
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"additionKey"]){
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"additionKey"];
}
... simply always set's additionKey to TRUE. If you would like to check if additionKey is set, do this ...
//the following should assign the keys if they don't exist
if (![[NSUserDefaults standardUserDefaults] objectForKey:#"additionKey"]){
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"additionKey"];
}
... boolForKey: documentation: If a boolean value is associated with defaultName in the user defaults, that value is returned. Otherwise, NO is returned.
Translated to human language - if there's an value associated with additionKey, this value is returned. If there's no associated value, NO/FALSE is returned.
So, your code does this - if value is not associated with additionKey or if it is set to NO, set it to YES. This leads to this - additionKey is always set to YES/TRUE.
Related
I am developing a game,in that i want to add the points continuously,for this i used plist but whenever the screen is disappear and starts then plist starts again.what to do?
Thanks in Advance.
To add more info to Ahmed's answer you should implement in your AppDelegate.m three methods like this:
AppDelegate.h
NSNumber *gamescore;
#property(nonatomic, strong) NSNumber *gamescore;
#define UIAppDelegate \
((AppDelegate *)[UIApplication sharedApplication].delegate)
AppDelegate.m
#synthesize gamescore;
- (BOOL) checkFirstRun {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *defaultcheck;
defaultcheck = [defaults objectForKey:#"GameScore"];
if (defaultcheck==nil) {
return TRUE;
} else {
return FALSE;
}
}
- (void) storeGlobalVars {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:gamescore forKey:#"GameScore"];
[defaults synchronize];
}
- (void) readGlobalVars {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
gamescore = [defaults objectForKey:#"GameScore"];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// ...
if ([self checkFirstRun]) {
// first run, lets create basic default values
gamescore = [NSNumber numberWithInt:0];
[self storeGlobalVars];
} else {
[self readGlobalVars];
}
// ...
Later in your application, after importing AppDelegate.h you can use the UIAppDelegate.gamescore to access the AppDelegate's property.
And you have to remember that gamescore is an NSNumber object, you have to manipulate it using NSNumber's numberWithInt and/or intValue.
The CheckFirstRun is needed because your user's device at application first run doesn't contain the default plist and the initial values, you have to create an initial set.
You can make AppDelegate variables and store it in them. There scope remains in the complete application until the application closes.
In AppDelegate.h
for example
NSString *string;
#property(nonatomic, strong) NSString *string;
In AppDelegate.m
#synthesize string;
in applicationDidFinishLaunchingWithOptions
string = #"";
And then is your classes
add #import "AppDelegate.h"
then in your code
((AppDelegate *)[UIApplication SharedApplication].Delegate).string = #"1";
The problem I get when I use the codes below is that the totalScore only updates when the viewDidLoad method in the secondViewController is called. So if I press the addOne button twice in a row the retrievingString is the same both times so it will just add one to the same existing value, because it haven't been updated in the secondViewController. How can I manage to update the totalScore in secondViewController each time the addOne button is pressed in firstViewController
firstViewController:
-(void)saveString:(NSString *)myString {
[[NSUserDefaults standardUserDefaults] setObject:myString forKey:#"firstScore"];
}
-(NSString *)retrieveString {
NSString *recoveredString = [[NSUserDefaults standardUserDefaults]objectForKey:#"totalScore"];
return recoveredString;
}
-(IBAction) addOne {
NSString *totalScore = [self retrieveString];
NSInteger totalScoreInt = [totalScore intValue];
NSInteger newScore = totalScoreInt+1;
NSString *newScoreString = [NSString stringWithFormat:#"%i", newScore];
[self saveString:newScoreString];
}
secondViewController:
-(void)saveString:(NSString *)myString {
[[NSUserDefaults standardUserDefaults] setObject:myString forKey:#"totalScore"];
}
-(NSString *)retrieveString {
NSString *recoveredString = [[NSUserDefaults standardUserDefaults]objectForKey:#"firstScore"];
return recoveredString;
}
-(void) viewDidLoad {
NSString *firstScore = [self retrieveString];
NSInteger firstScoreInt = [firstScore intValue];
NSInteger newTotalScore = firstScoreInt+1;
NSString *newTotalScoreString = [NSString stringWithFormat:#"%i", newTotalScore];
[self saveString:newTotalScoreString];
}
I hope I have explained the problem understandable, so maybe someone can help me with this.
If I'm understanding this correctly, the error that u'r making here is that since addOne function in your firstViewController is also updating the total score, but you are not saving the total score there. So in your saveString function in the firstViewController, add this line too :
[[NSUserDefaults standardUserDefaults] setObject:myString forKey:#"totalScore"];
Hope this helps
You are working with 2 different scores, taking the value from firstScore, increasing it and storing it into totalScore. Check what you are trying to store in each.
Here's an example of why your code fails:
firstScore is 1
totalScore is whatever
then you addOne, which takes firstScore (1), adds one to it (2), and stores in totalScore, which leaves it like this:
firstScore is 1
totalScore is 2
you addOne again, and it reads firstScore (1), adds one (2) and stores again, so nothing really changed.
I have a settings view where i have provided the option for users to change their settings.
Settings view have a table view having 2 UISwitches. I need help with NSUserdefaults method for saving states, and how I can write the code to load my values.
Here is my code so far
In `cellForRowAtIndexPath` method:
[cell.switchButton addTarget:self action:#selector(switchButtonTapped:) forControlEvents:UIControlEventValueChanged];
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"myBool"];
BOOL hBool = [[ NSUserDefaults standardUserDefaults] boolForKey:#"myBool"];
NSLog(#"tag %i", tappedSwitch.tag);
if (cell.switchButton.tag == 0) {
[cell.switchButton setOn:hBool];
}
return cell;
}
- (void) switchButtonTapped: (id) sender {
tappedSwitch = (UISwitch *) sender;
switch (tappedSwitch.tag) {
case 0:
passcodeSwitchIsOn = tappedSwitch.isOn;
if (passcodeSwitchIsOn) {
GCPINViewController *PIN = [[GCPINViewController alloc]
initWithNibName:nil
bundle:nil
mode:GCPINViewControllerModeCreate];
PIN.messageText = #"Enter a passcode";
PIN.errorText = #"The passcodes do not match";
PIN.title = #"Set Passcode";
PIN.verifyBlock = ^(NSString *code) {
NSLog(#"setting code: %#", code);
code = saveString;
return YES;
};
[PIN presentFromViewController:self animated:YES];
[PIN release];
// method of saving
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:passcodeSwitchIsOn forKey:#"myBool"];
[defaults synchronize];
}
break;
case 1:
bluetoothSwitchIsOn = tappedSwitch.isOn;
if (bluetoothSwitchIsOn) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Bluetooth Switch" message:#"Bluetooth Switch is ON" delegate:self cancelButtonTitle:#"Okay" otherButtonTitles:nil, nil];
[alert show];
[alert release];
}
break;
default:
break;
}
[settingsTable reloadData];
}
Create a reference to your user defaults:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
Set a value:
[defaults setBool:1 forKey:#"bluetooth"];
You can set this back to 0 when bluetooth is off:
[defaults setBool:0 forKey:#"bluetooth"];
The user default is identified by the string of your choice. In this case: #"bluetooth". The value for the default will be nil until you create it and set it to something else.
So you can say:
if (!bluetooth) // bluetooth is off
else // bluetooth is on
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
This can be used for handling your issue.
IBOutlet UISwitch * swi_yourswitch;
#property(nonatomic,retain) UISwitch * swi_yourswitch;
implimentation
#synthesise swi_yourswitch;
- (void) switchButtonTapped: (id) sender {
UISwitch * switchObj = (UISwitch*)sender;
if(switchObj == self.swi_yourswitch){
if (self.swi_yourswitch.on){
NSLog(#"On");
}
else{
NSLog(#"Off");
[self.swi_yourswitch setOn:0];
}
}
To store value
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"mybool"];
[[NSUserDefaults standardUserDefaults] synchronize];
To get stored value
[NSUserDefaults standardUserDefaults] valueForKey:#"mybool"]
I have a UITableView with a UISwitch button in it. I want that when I run my application default value of the UISwitch button should be ON. If I toggle the switch button from on to off the value in the plist should change to OFF. If I quit the app and then I again run my app the value should again default to ON in the plist.
I have tried using NSUserDefaults, it works i.e when I change the value from ON to OFF the value in NSUserDefaults also changes. But when the app is run again the default value is not set to ON.
- (void)viewDidLoad {
[super viewDidLoad];
settings = [[NSMutableArray alloc]initWithObjects:#"Clock",#"Time Format",#"Weather",#"Degrees",nil];
switchControl = [[UISwitch alloc]initWithFrame:CGRectMake(205, 110, 20, 15) ];
[self.switchControl addTarget:self action:#selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:switchControl];
NSString *viewdid = #"ON";
userdefaults = [NSUserDefaults standardUserDefaults];
[userdefaults setObject:viewdid forKey:#"stateOfSwitch"];
}
- (void)viewWillAppear:(BOOL)animated {
NSString *_value= [[NSUserDefaults standardUserDefaults] stringForKey:#"stateOfSwitch"];
if([_value compare:#"ON"] == NSOrderedSame){
[switchControl setOn:YES animated:YES];
}
else {
[switchControl setOn:NO animated:YES];
}
[super viewWillAppear:animated];
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 4;
}
-(void)switchChanged:(id)sender
{
app= (StopSnoozeAppDelegate*)[[UIApplication sharedApplication]delegate];
NSString *value = #"ON";
userdefaults = [NSUserDefaults standardUserDefaults];
if(!switchControl.on){
value = #"OFF";
[userdefaults setObject:value forKey:#"stateOfSwitch"];
}
[userdefaults setObject:value forKey:#"stateOfSwitch"];
[userdefaults synchronize];
}
//this is my second class where i am accessing my userdefaults key and depending on that hiding the views
- (void) viewWillAppear:(BOOL)animated
{
NSString *_value= [[NSUserDefaults standardUserDefaults] stringForKey:#"stateOfSwitch"];
if([_value compare:#"ON"] == NSOrderedSame){
newview.hidden = NO;
newnewview.hidden =NO;
firstview.hidden = NO;
secondview.hidden = NO;
thirdview.hidden = NO;
}
else if([_value compare:#"OFF"] == NSOrderedSame) {
newview.hidden = YES;
newnewview.hidden= YES;
firstview.hidden = YES;
secondview.hidden = YES;
thirdview.hidden = YES;
}
}
In appDelegate did finish loading write below statements
[[NSUserDefaults standardUserDefaults] setObject:#"OFF" forKey:#"stateOfSwitch"];
[[NSUserDefaults standardUserDefaults] synchronize];
May be you have not dump new values to plist?
Try to call [[NSUserDefaults standardUserDefaults] synchronize]; after setting new value to NSUserDefaults.
Either do
// set when launching the app
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[[NSUserDefaults standardUserDefaults] setObject:#"OFF" forKey:#"stateOfSwitch"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
or
// set when terminating
- (void)applicationWillTerminate:(UIApplication *)application
[[NSUserDefaults standardUserDefaults] setObject:#"OFF" forKey:#"stateOfSwitch"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Furthermore, you should test your NSString using isEqualToString: as compare: may lead to false positives (e.g. if one of the strings is nil).
You shouldn't store a string, which has the potential for parsing bugs, when NSUserDefaults is perfectly capable of storing BOOLs directly.
Just store it like this:
#define StateOfSwitch #"StateOfSwitch"
-(void)switchChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:switchControl.on forKey:StateOfSwitch];
[[NSUserDefaults standardUserDefaults] synchronize];
}
and then later, load the value like this:
switchControl.on = [[NSUserDefaults standardUserDefaults] boolForKey:StateOfSwitch];
(Note it's a good idea to never use string keys directly, it's better to #define them to prevent typo errors. Plus you get command + click to jump to definition, autocompletion, etc.)
which is the cleanest way to use something like a global variable? Normally, using a global variable is forbidden, but I don't know a better solution for accessing NSUserDefaults from different classes.
I read a bit and come up with this. I define a Contants.h and a Constants.m file and include them everywhere I need to.
//Constants.h
#import <Foundation/Foundation.h>
#interface Constants : NSObject {
extern NSUserDefaults *settings;
}
#end
.
//Constants.m
#implementation Constants
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"];
NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
[[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict];
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
#end
The problem here is that I want to initialize a value to my constant. I have no method in Constants.m. So my helper variables would also be globals?
One thing to mention: I think the global variable also has to be released?
Thanks for your help!
Edit:
#hotpaw2:
AppBundleSingleton.h:
#import <Foundation/Foundation.h>
#interface AppBundleSingleton : NSObject {
}
+ (AppBundleSingleton *)sharedAppBundleSingleton;
#end
AppBundleSingleton.m:
#import "AppBundleSingleton.h"
static AppBundleSingleton *sharedAppBundleSingleton = nil;
#implementation AppBundleSingleton
#pragma mark -
#pragma mark Singleton methods
+ (AppBundleSingleton *)sharedAppBundleSingleton {
#synchronized(self) {
if (sharedAppBundleSingleton == nil) {
sharedAppBundleSingleton = [[self alloc] init];
}
}
return sharedAppBundleSingleton;
}
+ (id)allocWithZone:(NSZone *)zone {
#synchronized(self) {
if (sharedAppBundleSingleton == nil) {
sharedAppBundleSingleton = [super allocWithZone:zone];
return sharedAppBundleSingleton; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
-(id)init {
self = [super init];
sharedAppBundleSingleton = self;
// Initialization code here
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"];
NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
[[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict];
return self;
}
#end
In my AppDelegate.m I have the following:
// ...
#include "AppBundleSingleton.h"
#implementation MyAppDelegate
// ...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
[AppBundleSingleton sharedAppBundleSingleton];
return YES;
}
// ...
#end
In my ViewController I query the values:
NSString *myString = [[NSUserDefaults standardUserDefaults] stringForKey:#"myKeyforString"];
Would that be a solution?
You don't need to use global variables - you can access the userdefaults from any class or object within your project like this:
BOOL foo = [[NSUserDefaults standardUserDefaults] boolForKey:#"bar"];
As long as you synchronize the userdefaults after every change you make, the data fetched in this way is consistent.
Global variables are not only NOT forbidden, but a required part of the ANSI C specification, which is a subset of Obj C. Both gcc and llvm fully support globals. They are often the smallest and fastest way to pass unprotected values around.
That said, explicit use of global variables are most probably not the solution to your problem. You have a problem quite well suited to the MVC paradigm. Place all your NSDefault code into a singleton model class (an M of the MVC), which can self-initialize on first access (the implementation may use a hidden global), and attach that object to the appDelegate where it can easily be obtained from anywhere. Then encapsulate all you default value read/writes as properties of that object.