• 从零开始学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

  • 相关阅读:
    高可用——Keepalived安装部署使用详解
    Java—File类详解及实践
    MySQL—设置数据库(库、表等)不区分大小写
    MySQL—Mysql与MariaDB启停命令的区别
    Linux—微服务启停shell脚本编写模板
    SpringBoot—集成AOP详解(面向切面编程Aspect)
    Java—Map集合详解
    手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊
    Java高效编程:总结分享
    Redis的几种应用实战
  • 原文地址:https://www.cnblogs.com/lovewx/p/3824249.html
Copyright © 2020-2023  润新知