在上一篇的学习中,我们知道了如何为一个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几乎都会使用到,有些保存的方法其实我们已经学习过了,在下一章会重点再介绍一次,希望下一篇会很快到来,我会努力的!