无法在视图之间读取NSUserDefaults数据

3
免责声明:我是个新手。
我正在编写一个算术闪卡应用作为学习项目。我有一个UITabViewController,底部的选项卡可以在几个不同的视图之间切换。一切都正常工作,直到我尝试在设置视图控制器中设置NSUserDefault布尔值,并尝试在Flashcards视图控制器中读取这些值时出现问题。
设置视图有一个开关来启用/禁用每个运算符(加法、减法等),如果启用了该类型的运算,则闪卡视图应随机呈现一个闪卡。
我认为我的错误在于我不理解数据封装、对象等核心概念。我会非常感谢任何帮助。
以下是设置视图控制器的代码。我甚至不确定如何将代码放入这个论坛,所以这可能是一个可笑的时刻……让我们看看:
//  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

并且...

/  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"];    
}
}

这里是“闪卡视图”页面...
//  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

并且...

//  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];
}
}

你尝试过调用 [[NSUserDefaults standardUserDefaults] synchronize] 吗?每次设置新值时都可以调用它。 - fabian789
2
@fabian:调用synchronize是一个很糟糕的错误,应该很少使用。它所做的只是创建不必要的开销。 - sudo rm -rf
你真的想每次加载视图时都将所有设置都设置为TRUE吗?如果您想测试键是否存在,应改用objectForKey:,它将返回nil(如果该键不存在)。此外,在Objective-C中,通常使用YESNO而不是TRUEFALSE(尽管后者也可以工作)。 - DarkDust
@sudo rm -rf 是正确的,只有在进入后台或放弃活动时才需要调用synchronize - DarkDust
3个回答

5

这里有几件事情需要做。首先,您需要更好地说明在用户没有做出任何明确决定之前的默认状态。其次,您需要知道何时应该从用户首选项中刷新应用内状态。

默认值初始化

第一项解决方案是将默认值放入用户首选项的注册域中。这是您在应用程序初始化期间执行的操作,而不是让您的视图在初始化时逐个检查首选项并更新它们。首选项系统会在很多地方查找数据,而它首先查找的是仅存储在内存中的注册域。这是您将每个用户首选项的默认(即未指定值表示“x”)值放置的位置。

您将使用的API是-[NSUserDefaults registerDefaults:],它接受一个NSDictionary值。要将默认值设置为YES(在Objective-C中,BOOL类型使用YESNO而不是TRUEFALSE),您将使用类似于以下内容的内容,通常在应用程序主类的+initialize方法中执行:

+ (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];
}

通常,您会将所有内容放入一个方法中,如下所示。因此,如果应用程序的其他部分希望对任何首选项使用默认非零值,则应将其添加到此组中,并将其放置在应用程序委托的+initialize方法中。
现在,当您的其他类使用[[NSUserDefault standardUserDefaults] boolForKey: @"additionKey"]并且没有为该键保存任何内容时,它们将获得上面提供的值。 刷新缓存值 因此,您有一个视图,用户可以在其中更改这些首选项。您的下一个任务是使上面的视图使用新首选项更新其成员变量。为此,我们可以使用委托或通知。在这种情况下,我选择通知,原因如下:
  1. 您正在使用NSUserDefaults,理论上可以在许多不同的地方进行更改。委派只会通知您一个对象所做的更改。
  2. NSUserDefaults已经实现了一个方便的通知,您可以观察它。
因此,在您的flashcardsViewController中,您将拥有以下几个方法:
- (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];
}

摘要

综合考虑这两个因素,您应该拥有实现此功能所需的一切。


糟糕,我对视图控制器的工作原理的理解是这样的:假设我点击了“设置”选项卡,那么“设置”的viewDidLoad方法将会运行。现在假设我点击了“闪卡”选项卡,那么“设置”的viewDidUnload方法将会运行,而“闪卡”的viewDidLoad方法将会运行。再次点击“设置”选项卡将会强制运行“闪卡”的viewDidUnload方法和“设置”的viewDidLoad方法。这样说对吗? - Sam
@Sam:是的,那是错误的。视图在第一次出现之前加载,但只有在必要时才会卸载。通常情况下,视图将一直保持加载状态,直到设备开始内存不足。另一方面,viewWillAppearviewWillDisappear方法将在每次视图循环中调用。 - e.James

4
除了Chiefly Izzy指出的“总是YES”的问题之外,您的-buttonClicked:方法没有从NSUserDefaults中读取新的值。这些值在[flashcardsViewController viewDidLoad]中被读取(一次)。如果它们在设置中更改,则在下一次加载(可能是下一次应用程序启动时)flashCardsViewController中将无法检测到更改。
最简单的方法是将代码移动到读取IsEnabled布尔值的-buttonClicked方法中,如下所示:
-(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 ...

3

这段代码...

//the following should assign the keys if they don't exist  
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"additionKey"]){
    [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"additionKey"];
}

...只需将additionKey始终设置为TRUE。如果您想检查additionKey是否已设置,请执行以下操作...

//the following should assign the keys if they don't exist  
if (![[NSUserDefaults standardUserDefaults] objectForKey:@"additionKey"]){
    [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"additionKey"];
}
< p >... boolForKey:文档:如果在用户默认设置中与defaultName关联了布尔值,则返回该值。否则,返回NO。

翻译成人类语言 - 如果与additionKey相关联的值存在,则返回该值。如果没有关联值,则返回NO / FALSE。

因此,您的代码执行以下操作 - 如果未与additionKey关联值或将其设置为NO,则将其设置为YES。这导致additionKey始终设置为YES / TRUE。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接