三十而立,从零开始学ios开发(二十):Application Settings and User Defaults(下)

在上一篇的学习中,我们知道了如何为一个App添加它的Settings设置项,在Settings设置项中我们可以添加哪些类型的控件,这些控件都是通过一个plist来进行管理的,我们只需对plist进行修改添加,就可以映射到Settings中。但是在上一篇中,我们并没有学习Settings和App的交互,在这一篇中我们将进行学习,如何在一个App中读取Settings中的值,如何在App中修改Settings中的值,好了,下面开始我们这次的学习。

1)NSUserDefaults
NSUserDefaults是ios自带的一个对象,它的主要作用对Settings中的变量(我们添加的控件)进行取值和赋值。在上一篇中,我们在创建plist的时候,每一个Item都有一个Key,NSUserDefaults就是根据这个Key来找到对象,然后取得值,或者赋值。这个其实就是一个key-value对,NSUserDefaults在用法上和NSDictionary是一样的,因此它也有很多类似的对象例如:objectForKey,intForKey,floatForKey,boolForKey,这些都很简单。另外NSUserDefaults是一个单例模式(Singleton),也就是说在整个app中只有一个NSUserDefaults对象存在,这样可以避免在2个地方同时对一个Item进行操作的情况,在程序中,我们使用standardUserDefaults方法来取得NSUserDefaults对象:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

2)从Settings中读取值
打开BIDMainViewController.h,添加如下代码

#import "BIDFlipsideViewController.h"
#define kUsernameKey        @"username"
#define kPasswordKey        @"password"
#define kProtocolKey        @"protocol"
#define kWarpDriveKey       @"warp"
#define kWarpFactorKey      @"warpFactor"
#define kFavoriteTeaKey     @"favoriteTea"
#define kFavoriteCandyKey   @"favoriteCandy"
#define kFavoriteGameKey    @"favoriteGame"
#define kFavoriteExcuseKey  @"favoriteExcuse"
#define kFavoriteSinKey     @"favoriteSin"

@interface BIDMainViewController : UIViewController <BIDFlipsideViewControllerDelegate>

@property (weak, nonatomic) IBOutlet UILabel *usernameLabel;
@property (weak, nonatomic) IBOutlet UILabel *passwordLabel;
@property (weak, nonatomic) IBOutlet UILabel *protocolLabel;
@property (weak, nonatomic) IBOutlet UILabel *warpDriveLabel;
@property (weak, nonatomic) IBOutlet UILabel *warpFactorLabel;
@property (weak, nonatomic) IBOutlet UILabel *favoriteTeaLabel;
@property (weak, nonatomic) IBOutlet UILabel *favoriteCandyLabel;
@property (weak, nonatomic) IBOutlet UILabel *favoriteGameLabel;
@property (weak, nonatomic) IBOutlet UILabel *favoriteExcuseLabel;
@property (weak, nonatomic) IBOutlet UILabel *favoriteSinLabel;

- (void)refreshFields;

@end

上面的代码定义了10个常量Key,用来在之后的代码中根据Key获取Settings中的值,接着声明10个IBOutlet,且他们都是指向Label的,最后声明了一个refreshField方法,用于从Settings中读取值然后赋给Label。

保存上面的code,然后在Project navigator中选中MainStoryboard.storyboard,在Layout area中会显示2个View,分别是Main View和FlipSide View,我们选中Main View,然后打开Attribute inspector,找到Backgrund,将其背景色变为白色

在Main View的右下角,有一个Info button(一个圆圈,中间有一个字母i),选中它,然后在Attribute inspector中将其Type改成“Info Dark”

这样就能够很容易的找到它所在的地方了

如果你觉得直接在Main View中选择一个白色的Info button很困难,那么你可以打开Main View Controller Scene,在里面你可以方便的找到它

好,接下来我们就要往Main View中拖控件了,一共拖20个Label,其中十个Label为静态的,仅仅显示文字,另外十个需要与刚才定义的IBOutlet关联起来,用于显示Settings中的值。

根据下图的样子,将Label拖入到Main View中

排列什么的无所谓,自己喜欢就好,主要是右边的10个Label,他们的宽度都是到最右边出现辅助线的位置,以保证能够最大限度容纳字符。

好了,下面的工作就是关联IBOutlet和view上面的Label了,一共要关联10个Label,选中Main View Controller Scene中的Main View Control,control-drag到Label上(右边的一排Label),在弹出的框中选中相对应的IBOutlet

好了,保存所有的修改,下面开始编码。

打开BIDMainViewController.m,添加以下code

@implementation BIDMainViewController

- (void)refreshFields {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    self.usernameLabel.text = [defaults objectForKey:kUsernameKey];
    self.passwordLabel.text = [defaults objectForKey:kPasswordKey];
    self.protocolLabel.text = [defaults objectForKey:kProtocolKey];
    self.warpDriveLabel.text = [defaults boolForKey:kWarpDriveKey] ? @"Enabled" : @"Disabled";
    self.warpFactorLabel.text = [[defaults objectForKey:kWarpFactorKey] stringValue];
    self.favoriteTeaLabel.text = [defaults objectForKey:kFavoriteTeaKey];
    self.favoriteCandyLabel.text = [defaults objectForKey:kFavoriteCandyKey];
    self.favoriteGameLabel.text = [defaults objectForKey:kFavoriteGameKey];
    self.favoriteExcuseLabel.text = [defaults objectForKey:kFavoriteExcuseKey];
    self.favoriteSinLabel.text = [defaults objectForKey:kFavoriteSinKey];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self refreshFields];
}

首先我们实现了refreshFields方法,里面首先声明了NSUserDefaults,用于获取Settings中的值,之后调用objextForKey,通过Key来获取10个Label的值,有2个比较特殊,第一个是kWarpDriveKey,由于WarpDrive是一个Switch,它返回的是一样bool型,因此通过三元运算符来判断YES或NO,如果是YES,返回“Enabled”,如果是NO,则返回“Disabled”。另一个比较特殊的是WarpFactor,由于WarpFactor是一个slider,返回的是一个int型,所以通过stringValue将int型转换为string输出。

然后我们重载了viewDidAppear方法,在view出现时调用refreshFields方法。

接着在BIDMainViewController.m中找到flipsideViewControllerDidFinish方法,添加如下code

- (void)flipsideViewControllerDidFinish:(BIDFlipsideViewController *)controller
{
    [self refreshFields];
    [self dismissViewControllerAnimated:YES completion:nil];
}

在Main View的右下角有一个info button,点击它会切换到flipsideView,当从flipsideView切换回来的时候,就会调用上面的这个方法,然后再次刷新Settings中的数据。

好了,编译运行一下程序,程序启动后,在Settings中为AppSettings中的每一项赋值,然后打开AppSettings程序,就可以看到一下类似的截图了。

 

3)将值写入Settings
刚才我们通过NSUserDefaults从Settings读取了值,现在我们将学习如何在程序中设置值,然后再写入到Settings中。

打开MainStoryboard.storyboard,在layout area中,将Flipside View Controller布局成如下样子

将View的背景色改成Light Gray color,View的title改成“Warp Settings”,添加了2个Label,添加了一个Switch和Slider,其中,Slider的左右两个图片是在其Attributs inspector中设置的

然后将slider的最小值设为1,最大值设为10,当前值设为5,同样也是在Attributes inspector中进行设置

根据View中的内容,我们可以看出是针对Settings中的switch(WarpDrive)和slider(Warp Factor),打开BIDFlipsideViewController.h,添加如下code

#import <UIKit/UIKit.h>

@class BIDFlipsideViewController;

@protocol BIDFlipsideViewControllerDelegate
- (void)flipsideViewControllerDidFinish:(BIDFlipsideViewController *)controller;
@end

@interface BIDFlipsideViewController : UIViewController

@property (weak, nonatomic) id <BIDFlipsideViewControllerDelegate> delegate;
@property (weak, nonatomic) IBOutlet UISwitch *engineSwitch;
@property (weak, nonatomic) IBOutlet UISlider *warpFactorSlider;

- (void)refreshFields;
- (IBAction)engineSwitchTapped;
- (IBAction)warpSliderTouched;

- (IBAction)done:(id)sender;

@end

两个IBOutlet自然是指向View上的Switch和Slider的,refreshFields是用来刷新Flipside上的数据的,2个IBAction,当swtich发生改变时,触发engineSwitchTapped事件,当slider发生改变时,触发warpSliderTouched事件。

下面就是绑定了,展开Flipside View Controller Scene

然后选中Flipside View Controller根节点,control-drag到switch上,在弹出的框中选择engineSwitch
同样的方法,control-drag到slider上,选中warpFactorSlider

然后绑定事件,这次在Flipside View Controller选中switch,然后control-drag到Flipside View Controller Scene中的根节点Flipside View Controller上,会弹出一个框

选中enginSwitchTapped,同学选择slider,control-drag,选择warpSliderTouched

好了,界面上的操作全部完成了,下面开始编码,打开BIDFlipsideViewController.m,添加如下代码

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self refreshFields];
}

......

- (void)refreshFields
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    self.engineSwitch.on = [defaults boolForKey:kWarpDriveKey];
    self.warpFactorSlider.value = [defaults floatForKey:kWarpFactorKey];
}

- (IBAction)engineSwitchTapped
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:self.engineSwitch.on forKey:kWarpDriveKey];
}

- (IBAction)warpSliderTouched
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setFloat:self.warpFactorSlider.value forKey:kWarpFactorKey];
}

首先在viewDidLoad中调用refreshFields的方法,接着实现了refreshFields,用于读取Settings中的值并显示在界面上,然后实现了2个IBAction,第一个用于保存Swtich的值,另一个保存Slider的值,2个方法都很简单,看代码就能看懂了。

好,保存编译运行程序,在主界面中点击右下角的info button,切换到flip view

flip view上显示了Settings中Warp Drive的值和Warp Factor的值,然后我们调整一下,将Warp Engines设为ON,然后改变一下Warp Factor的位置,例如

然后按左上角的Done,返回main view,可以看到Warp Drive和Warp Factor的值也相应发生了改变

3)设置默认值
好了,到目前位置,我们已经可以做很多事情了,在App中读取Settings中的值,在App中改变Settings中的值并保存回Settings中,但是大家有没有发现,当你们第一运行app时,main view中其实是空的,什么都没有,只有当我们退出程序,在Settings中设置好值以后,再回到App中,此时main view中的值才会有。其实这个情况是正确的,但是我们更希望能够有一个默认值存在,我们无法在Settings.bundle中设置默认值,但是NSUserDefaults为我们提供了方法,打开BIDAppDelegate.m,添加如下代码

#import "BIDAppDelegate.h"
#import "BIDMainViewController.h"

@implementation BIDAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSDictionary *defaults = @{kWarpDriveKey: @YES,
                               kWarpFactorKey: @5,
                               kFavoriteSinKey: @"Greed"};
    
    [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
    return YES;
}

从代码中可以看到,NSUserDefaults通过registerDefaults方法设置默认值,它的参数是一个NSDictionary,在NSDictionary设置了3个对象的默认值。好了,在重新编译运行程序之前,请确保将模拟器中的AppSettings删除,再编译运行,效果如下

可以看到,Warp Drive, Warp Factor, Favorite Sin三个对象都有默认值,和我们的预期是一样的。

4)数据的同步
相信大家现在的手机至少是ios4以上的版本了,从ios4开始,ios就支持多任务了(虽然表面上是支持的),用户可以在多个app之间进行切换。我们这个app当然也可以,但是有一个问题,我们运行程序,app默认界面如下

接着按Home键,回到桌面进入Settings,为Username

然后退出Settings,再进入AppSettings,你会发现刚才输入的username并没有显示出来

ok,好,我们现在就来解决这个问题。首先简单的说一下原理,这里用到了ios的一个机制:Notification。当一个后台的程序被激活后,它将收到一个notification:UIApplicationWillEnterForegroundNotification,表示app即将显示到前台来。我们首先定义一个方法,当发生notification时会调用该方法,分别打开BIDMainViewController.m和BIDFlipsideViewController.m,分别添加如下代码

- (void)applicationWillEnterForeground:(NSNotification *)notification
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults synchronize];
    [self refreshFields];
}

NSUserDefaults的synchronize方法是用来同步Settings中的值的,当值同步完后,调用refreshFields方法,显示到view中。

再分别为BIDMainViewController.m和BIDFlipsideViewController.m添加如下方法

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillEnterForeground:)
                                                 name:UIApplicationWillEnterForegroundNotification
                                               object:app];
}

该方法用于监听ios发出的notification,当监听到UIApplicationWillEnterForegroundNotification通知后,就调用applicationWillEnterForeground方法。

我们还要更新2个已经写好的方法,engineSwitchTapped和warpSliderTouched(在BIDFlipsideViewController.m中),修改如下

- (IBAction)engineSwitchTapped
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:self.engineSwitch.on forKey:kWarpDriveKey];
    [defaults synchronize];
}

- (IBAction)warpSliderTouched
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setFloat:self.warpFactorSlider.value forKey:kWarpFactorKey];
    [defaults synchronize];
}

新增了同步,每当值发生改变,就同步一下。

我们最后还要添加一个方法,用于释放notification,还是分别为BIDMainViewController.m和BIDFlipsideViewController.m添加如下方法

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

当view消失后(进入后台或者程序退出),将notification释放。

好了,再次编译运行app,按Home键回到桌面,进入Settings修改值再回到AppSettings,会发现相对于的值发生了改变。

5)总结
应该说,这篇以及上一篇关于Settings的内容都已经讲完了,涵盖了绝大部分关于Settings的操作,这部分的内容在程序设计开发的过程中还是相对有用的,对我来说收获很大。下一篇开始,我们将重点讲解ios对于数据的保存功能是如何实现的,又是一个很有用的内容,每个app几乎都会使用到,有些保存的方法其实我们已经学习过了,在下一章会重点再介绍一次,希望下一篇会很快到来,我会努力的!


AppSettings_all.zip

 

 

 

 

posted @ 2013-06-12 23:24  minglz  阅读(6163)  评论(30编辑  收藏  举报