【链接】iOS开发--应用设置及用户默认设置【1、bundle
http://www.jianshu.com/p/6f2913f6b218
在iphone里面,应用都会在“设置”里面有个专属的应用设置,选择该菜单界面,用户便可以在其中输入和更改各种选项,协助用户更便捷设置个人喜好与习惯。
在这一节中,希望能通过对捆绑包(bundle)的介绍以及plist文件等的运用,探讨用户默认机制中,应用设置的开发与调用。
【本次开发环境: Xcode:7.2 iOS Simulator:iphone6S plus By:啊左】
一、设置捆绑包(bundle)介绍与界面展示
设置捆绑包(bundle)是应用自带的一组文件,用户可以通过它进行输入与更改应用的偏好设置。例如【图1】,最底下就是开发的命名为:“应用设置”的app应用。
设置应用充当着ios用户默认设置(User Ddefault)机制的通用用户界面的角色。用户默认设置是保存和获取偏好设置的系统的一部分。在ios应用中,用户默认设置由NSUserDefaults类实现(同样,在mac中,也是通过这个类来保存和读取偏好设置的)。
NSUserDefaults与NSDictinary?
应用通过NSUserDefaults用键值对的方式来读取和保存偏好设置数据,与通过键从NSDictionary对象中获取数据一样,不同之处在于NSUserDefaults中的数据会被持久保存到文件系统中,而不是存储到内存中的对象实例中。
本节将通过开发一个应用,添加并配置一个bundle包。 然后从应用访问并且编辑这些偏好设置,以及从系统偏好设置中编辑,使app控件数据发生变化。
在这个过程,进行数据同步与更新。
设置界面【图2】如下:
【图2 应用设置界面 】(此app名称就叫“应用设置”。。 其中,More为最后一个选项的子视图)
app 应用的界面【图3】如下:
【图3 app应用界面】
所以,就开始创建项目吧~~
二、创建boundle包
1、首先通过下面这个图标创建一个叫“应用设置”的项目,记得将Devices勾选为iphone或是universal,取消勾选Use CoreData。
2、创建bundle捆绑包:
点击“应用设置”文件夹,然后从左上角选择File->New->File,选择ios部分中的Resource,选择设置Bundle图标(参见图4 )点击Next,名字保留默认的Settings,点击Create。
此时可以看到这样的一个“Setting.bundle”捆绑包
,展开.bundle设置,能看到"en.Iproj"这个文件夹,主要用与本地化应用,本文不讨论本地化应用这个内容,主要介绍Root.plist。
3、设置属性列表
点击Root.plist,查看编辑器窗口,可以看到Xcode的属性列表编辑器(参见图5),
如果你的编辑窗口与图看起来不一样,右键空白处,在弹出的菜单中选择:"Show Raw Keys/Values"。如下图:
【注意:本文讨论的属性列表plist文件中的图片,都是以"Show Raw Keys/Values"】
查看plist中各项组织结构。属性列表本质上就是字典。在存储的各个条目的类型与值,都要通过特定的键key来检索他们,这种使用方法与NSDictionaryBoolean、Data、Date、Number、String可以保存数据;另外,除了Dictionary也可以做为保存字典的节点外,还有Array节点,也是可以储存含有其他节点的有序列表。其中,其中Dictionary和Array是唯一能够包含其他节点的属性列表节点类型。
(注:虽然平时用的NSDictionary可以使用大多数对象做为键,但属性列表中的Dictionary节点中键必须为字符串类型的,但是可以选择任意节点类型做为该键的值。)
下面介绍Root.plist中各项的设置与编辑
其中可以看到StringsTable这个条目,用于将应用转换为另一种语言,本文不会使用到它,但是也可以留着,虽然没有多大用。。
除了StringsTable,属性列表还有一个名为“PreferenceSpecifiers”的节点,是一个数组,这个数组节点保存的一组Dictionary节点,每个Dictionary节点都代表着用户可修改的一个偏好设置项或用户可以访问的一个设置页面的子视图。
你会注意到这个属性列表模板中,有4个Item节点,在本应用中没有实际作用,所以分别点击Item1到
3,按delet依次删除它们,只留下Item0。
(技巧:要在属性列表中选择一项,最好是单击Key列的一端或者另一端,否则容易打开Key列的下拉菜单。)
点击Item0左边的三角形展开, 可以看到2行数据。其中Type键的值PSGroupSpecifier说明该条目是一个新分组的开始,紧跟其后的每个条目都会是此分组的一部分,直到有个新的Type键的值为PSGroupSpecifier才开始另一个分组。因此在PSGroupSpecifier中的Item0在属性列表中必须始终为PSGroupSpecifier类型,这样确保每个设置列表中都会至少有一个分组存在。
Item0中的Title键,用与设置这个组上的标题。后面我们可以在系统设置上面看到。
仔细观察Item0(Group - Group)中,第一个Group代表Type项的值,第二个Group代表Titile项的值。这是Xcode有助于只管观察捆绑包内容的便捷方式。
我们将Item0中的Title将它从Group改为Group Info。
下面我们在设置中添加一个让用户输入账号和密码的2个文本框。
首先点击Item0左边展开的三角形,使它关闭,然后选择Item0按下return键。
此时添加了一个新的与Item0的同级行,出现一个下拉菜单【图7】,显示默认值TextField。选择TextField,让菜单消失。
展开Item1,可以看到如【图8】,把它的标题Title键的值改为zhanghao(用于显示在设置中),Key键的值改为ZH(做为我们获取和编辑这个文本框的键,工作方式与Dictionary相似)
在Item的最后一行,也就是Key这行,按下return键添加一个新行,将其键设置为AutocapitalizationType,并且点击这一行最右边的选择项
,选择Words(表示文本框用户输入本文本框时,自动将每个单词改成首字母大写),也可以自己输入。
同理,添加AutocorrectionType选择No(表示不会自动更正输入到该文本框中的值
),也可以自己输入。
(记住标题是用来显示在设置屏幕上面的;键是用来存储值的;)
接下来添加密码这一文本框栏,可以通过添加Item1的方法,添加Item2条目,每一行的条目数据如【图10】,其中IsSecure为Boolean,表示此文本框是隐藏用户输入文字的密码框。(当然,Item2的创建也可以通过点击Item1,按Command+C,再按Command+V,复制出相同的新条目Item2,然后展开Item2修改其中的值,再增加IsSecure项。)
4、添加应用的图标,运行看系统设置中的变化。
点击文件夹中的Assets.xcassets。在点击里面的AppIcon项,如下【图11】,可以看到从左到右三类图标的设置。
第一个用来在iphone的主屏幕上显示你的应用;第二个会在iphone上使用Spolight搜索到应用时出现;第三个则显示在“设置”应用中;
如果需要自己处理图片,可以点击需要设置的方框,如图11,Siz为60x60,但是它的要求是2x的,因此需要图片大小为120x120的。
(可以用mac自带的图片浏览器处理:双击打开待处理的图片,点击屏幕上方:工具->调整大小,在填进120x120时,记得选择的单位是像素!)
此时我们只需要设置系统中应用“设置”的图标即可。按Command+R运行项目~~~
这时候,模拟器打开的是一个空白的项目,不急。我们先点击模拟器然后按Command+Shift+H,返回模拟器的屏幕主页,在界面上面找到与手机里面一样的应用设置:
,这时候,我们拉到最下面,可以看到多出了这一行:
单击后,如下图,可以我们创建的账号和密码文本框输入:
这就是以上通过bundle包进行设置的方法展示。
下面我们继续添加几项。
Item3:多值字段。
折叠Item2之后点击,按下return键添加Item3,在Key这一列弹出的菜单中选择MultiValue,单击左边三角形展开Item3,用之前编辑列表的方法,编辑增加以下6行条目,使其如下图内容一致。
其中,Ttiles数组,用于保存可供用户选择的一组值;
Values数组,用于保存用户默认设置中存储的一组值;
Ttiles与Values中的每一项都是对应的,因此,如果用户选择第一项,设置应用实际保存的是Values数组的第一个值,但用户看到的是Titles的第一项。因此这种Ttiles/Values对方便为用户提供易于理解选择。创建Item3时,最下面的“Default Value”行为Xcode自动添加的,代表默认值,将其值设为第一个的Ensign即可。
这就是多值字段的创建。
Item4:开关设置
用闭合Item3,按下return键,创建Item4,在弹出的菜单选择:“Toggle Switch”,设置为以下内容
接下来添加滑动条:
按照以上方法,分别创建Item5和Item6:如【图15】
Item5表示设置另外一个分组,且标题为“Warp Factor”。
Item6中,下面值分别代表,
Minimum Value:最小值; Maximum Value:最大值;
Min Value Image Filename:滑块最左端图标; Max Value Image Filename:滑块最右端图标;
(注意:滑块2个图标文件 tag_remove_24 和 tag_add_24 不能只放在主文件夹中,而是需要放在Settings捆绑包中,才能够通过 Min/Max Value Image Filename 设置使用。)
此时可以运行看看效果。
有时候,我们需要在主设置视图中添加子视图。例如说,当我们希望滑块与账号密码的设置不在同一个视图界面,而是通过进入另一个子视图设置。
我们可以向捆绑包添加2个新的Item如下:
【图17】(Item7表示添加一个新的分组,可以点击复制Item0然后粘贴添加新的分组Item7,Item8表子视图,其中Filename对应的More表示子视图由“More.plist”加载。)
接下来,我们需要新增一个名为“More.plist”属性文件,可以先添加放在主文件里。
然后点击“Root.plist”中的Preference Items项,然后复制到More.plist属性列表上面,删除除了Item5和Itme6外其他项,如图:
需要注意的是,Xcode中不允许直接向设置捆绑包添加新文件,因此需要通过右键"Setting.bundle"选择“show in Finder”打开窗口,然后把More.plist文件拖拽进去。
现在我们以后完成设置捆绑包的基本相关操作了。效果如【图20】,我们可以编译,运行测试项目应用的系统设置。也可以自己动手试试更改属性列表中的其他项。
下一节我们将探讨如何读取应用中的设置。
一、获取用户设置
1、NSUserDefaults类的介绍
访问用户的设置需要调用NSUserDefaults类,NSUserDefaults为单例类,需要调用类方法standardUserDefaults来获取指向标准用户默认设置的指针:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
defaults可以像NSDictionary类一样进方法的调用,例如“objectForKey:”,会返回一个Object-C对象,如NSString、NSDate、NSNumber。若需要获取整型、浮点型等方式,可以用“intForKey:”、“floatForKey”、“boolForKey”等其他方法。
2、键值
在上一节中,plist属性列表中的偏好设置字段,每个条目都有特定的键,例如Item1对应的是“officer”、滑块Item6对应的是“SliderValue”,这些键都是做为调用该项的ID。
因此我们可以先把这些键放在自己创建的头文件"settingHead.h"中,便于调用。内容如下:
#ifndef settingHead_h #define settingHead_h #define Officer @"officer" #define AuthorizationCode @"authorizationCode" #define Rank @"rank" #define IsWarp @"IsWarp" #define SliderValue @"SliderValue" #define moreFactor @"MoresliderValue" #endif /* settingHead_h */
3、关联相关控件
接下来,我们使用Main.storyboard创建14个label、一个开关、一个滑块,为左边8个label修改为Officer、code等内容如下:
【图2 Main.storyboard设置界面】
确认该视图关联的是ViewController,然后打开辅助编辑器(或者在
Main.storyboard界面按下option+command+return),按住control键分别从Main.storyboard图标
拖动到ViewController.h中,为右边6个label以及滑块、开关添加在ViewController.h的接口,代码如下:
#import <UIKit/UIKit.h> @interface ViewController : UIViewController //5个label接口 @property (weak, nonatomic) IBOutlet UILabel *label1; @property (weak, nonatomic) IBOutlet UILabel *label2; @property (weak, nonatomic) IBOutlet UILabel *label3; @property (weak, nonatomic) IBOutlet UILabel *label4; @property (weak, nonatomic) IBOutlet UILabel *label5; //滑块、开关接口 @property (weak, nonatomic) IBOutlet UISwitch *enginesSwitch; @property (weak, nonatomic) IBOutlet UISlider *WarpSlider; //滑块、开关的接口方法(修改用户默认设置) - (IBAction)enginesSwitchBtn:(UISwitch *)sender; - (IBAction)WarpSliderBtn:(UISlider *)sender; @end
在ViewController.m中,敲入: #import "settingHead.h" 添加键集合的头文件,然后添加一个更新app界面数据的方法: -(void)refreshValue ,代码如下:
1 #import "ViewController.h" 2 #import "settingHead.h" 3 4 @implementation ViewController 5 6 - (void)viewDidLoad { 7 [super viewDidLoad]; 8 self.view.backgroundColor = [UIColor orangeColor]; 9 [self refreshValue]; 10 } 11 12 -(void)refreshValue 13 { 14 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 15 self.label1.text = [defaults objectForKey:Officer]; 16 self.label2.text = [defaults objectForKey:AuthorizationCode]; 17 self.label3.text = [defaults objectForKey:Rank]; 18 self.label4.text = [defaults boolForKey:IsWarp]?@"Enable":@"Disable"; 19 self.label5.text = [[defaults objectForKey:SliderValue] stringValue]; 20 21 self.enginesSwitch.on = [defaults boolForKey:IsWarp]; 22 self.WarpSlider.value = [defaults floatForKey:SliderValue]; 23 }
在以上的refreshValue方法,首先通过调用NSUserDefaults类获取了标准用户默认设置,然后调用objectForKey方法访问获取对应键值的内容、
需要注意的是label1到label5,除了label4外,都是以NSString对象的形式返回,label4中defaults调用键值为IsWarp的方法返回的是bool值。因此我们得到结果后给予判断再赋值给label4的文本;
而label5由于是内容为滑块的值,是以NSNumber形式返回的,因此需要调用改对象的stringValue方法来获取它存储的值的字符串表示。
二、在应用中修改用户默认设置
应用中修改用户默认设置所调用的方法为:“setObject forKey: ”,在在ViewController.m中,需要在关联的开关滑块方法WarpSliderBtn、enginesSwitchBtn中添加如下代码:
1 - (IBAction)WarpSliderBtn:(UISlider *)sender { 2 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 3 [defaults setFloat:self.WarpSlider.value forKey:SliderValue]; 4 [defaults synchronize]; 5 } 6 - (IBAction)enginesSwitchBtn:(UISwitch *)sender { 7 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 8 [defaults setBool:self.enginesSwitch.on forKey:IsWarp]; 9 [defaults synchronize]; 10 }
运行、我们可以看到如下界面:
【图3 】
我们可以看到,即使我们为Multi Value、IsWarp设置默认值得,依然不会显示在我们的应用中,这是因为:我们的应用完全不知道设置捆绑包中已保存的该应用的偏好设置,即使我们在 设置中设置好相应的值,然后再运行一次可以看到应用上显示值,但是当我们删除掉运用再运行一次,还是显示为空值,而不能看到默认值。
我们可以作如下处理:如果我们尝试查找未设置的键/值,但该键至少会有一个默认值,可以用registerDefaults:方法,为了使得改设置在整个运用中都能有效,最好在应用启动时就调用它
因此,点击AppDelegate.m文件,添加"settingHead.h”头文件,然后在didFinishLaunchingWithOptions中键入以下代码:
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 2 // Override point for customization after application launch. 3 4 NSDictionary *dict = @{@"warp":@YES, 5 @"warpFactor":@5, 6 }; 7 [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; 8 return YES; 9 }
按下command+shift+H ,在ios模拟器主页上面长按我们创建的app,删除它,然后按下command+R再运行一次我们可以看到刚刚我们通过registerDefaults:方法设置的默认值。
三、同步系统设置与应用上的数据
现在我们可以尝试下通过调整应用来修改系统设置的值,但是奇怪的是:当我们修改系统设置,再打开app应用上面的数据也没有变化,当我们修改app上面的滑块与开关,再查看系统设置也是没有变化。
这也是ios系统的一个特点:当应用在运行时返回主屏幕,并不会退出应用,而是由操作系统在后台将其暂停了,这样它就可以随时能够快速启动,因为用户在 切换应用时,重新唤醒一个应用比从新启动一个应用要省下很多时间,这使得ios系统下应用的切换更加高效快捷。这也是后台应用的基本知识。
那么在这个例子中,我们应该如何同步更新数据呢,其实只要在唤醒的时候添加一个功能:重新加载同步用户偏好设置并重新显示它们的内容。
接下来我们需要运用到“通知”这一个对象之间进行通信的轻量级机制。通知中心是一个单例对象,作用是在对象之间传送通知。“通知”通常是在某些事件发生时发送的说明,UIApplicaton类会发送大量的通知(可以从Xcode的文档阅读器中找到这些更加详细的内容,在UIApplicaton页面的底部)。
应用在回到前台时,刷新一下它显示的内容,因此我们需要调用名为 “UIApplicationWillEnterForegroundNotification”的通知,编写viewWillAppear:方法时,我们会订阅该通知,并告诉通知中心该通知出现时需要调用的方法,这个方法我们可以命名为:“applicationwillenterforegound”、
点击ViewController.m,添加以下代码:
1 -(void)applicationwillenterforegound:(NSNotification *)notification 2 { 3 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 4 //进行应用设置与控件上的数据同步 5 [defaults synchronize]; 6 [self refreshValue]; 7 }
其中,我们可以看到defaults调用synchronize方法,该方法强制用户默认值保存尚未保存的修改,然后从存储中重新加载所有未修改的 偏好设置。事实上,这是在强制它重新读取已保存的偏好设置,从而获取设置应用中所作的修改,然后调用refreshValue:方法来个更新显示的内容。
Now,我们需要在ViewController.m中实现viewWillAppear:方法,使得该控制器订阅我们关注的“UIApplicationWillEnterForegroundNotification”通知。如下:
1 -(void)viewWillAppear:(BOOL)animated 2 { 3 [super viewWillAppear:animated]; 4 //注册观测者 5 UIApplication *app = [UIApplication sharedApplication]; 6 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationwillenterforegound:) name:UIApplicationWillEnterForegroundNotification object:app]; 7 }
在该addObserver: selector: name: object: 方法中,
observer传递的是self,也就是我们的控制器本身;
而selector,调用的是我们自己写的选择器方法,用于告诉通知中心在该通知发出时调用该方法;
name是我们要接収的通知的名称,一般可以在Xcode的文档阅读器中找到这些更加详细的内容;
最后一个参数app,是我们关心的获得通知的来源对象,如果改为nil,表示只要有方法发出“UIApplicationWillEnterForegroundNotification”通知,我们就会得到通知。
这样,我们就完成了,回到app前台重新加载内容的开发。
那么当用户在应用中操作控件,又怎样把内容同步到系统的默认设置里面呢?很简单,只要在我们设置的控件,开关与滑块的方法中添加一行 “[defaults synchronize]” 即可。
- (IBAction)WarpSliderBtn:(UISlider *)sender { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setFloat:self.WarpSlider.value forKey:SliderValue]; [defaults synchronize]; } - (IBAction)enginesSwitchBtn:(UISwitch *)sender { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setBool:self.enginesSwitch.on forKey:IsWarp]; [defaults synchronize]; }
【注意:调用synchronize:方法的开销会很大,因为要比较应用以及系统设置中的所有用户偏好设置,因此应该尽量减少对synchronize的调用,不过像我们此次的项目中,在响应每个用户操作时调用它一次,并不会造成任何显著的性能问题。】
当然,为了使得系统的工作过程更加清晰,我们可以在通知系统不需要接收到通知时,在viewDidDisappear中撤销注册,因此我们可以添加代码如下
1 -(void)viewDidDisappear:(BOOL)animated 2 { 3 [super viewDidDisappear:animated]; 4 /* 5 注销监听 也可以用removeObserver:self name: object:方法来撤销对特定通知的订阅,但以下这种更加方便:直接告知确保通知中心彻底忘记我们的observer的方法,不管注册它是为了 接收多少种通知。 6 */ 7 [[NSNotificationCenter defaultCenter] removeObserver:self]; 8 }
按下“command+R”运行。在应用的空间【参照图1】以及系统设置上面编辑,以及切换时查看同步效果。
做完以上工作,我们已经可以了解了设置应用和用户默认机制的基本概念,懂得如何添加捆绑包,如何为应用的偏好设置构建结构化的视图,通过NSUserDedeults读写偏好设置以及修改设置内容,并且开发了相关的项目。