简体   繁体   中英

Can't read NSUserDefaults data between views

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 (ie 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:

  1. You're using NSUserDefaults, which can theoretically change in many different places. Delegation would only inform you of changes made by one object.
  2. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM