一个 java 程序员自学 IOS 开发之路(七)
2015/11/2
Day 30
今天学习UIPickerView ,UIDatePicker
他们的使用方法与 UITableView及其类似,实现数据源方法,代理方法就能显示数据
一.UIPickerView
1.UIPickerView 的常见属性
// 数据源( 用来告诉UIPickerView 有多少列多少行)
@property(nonatomic,assign) id<UIPickerViewDataSource> dataSource;
// 代理( 用来告诉UIPickerView 每1 列的每1 行显示什么内容, 监听UIPickerView 的选择)
@property(nonatomic,assign) id<UIPickerViewDelegate> delegate;
// 是否要显示选中的指示器
@property(nonatomic) BOOL showsSelectionIndicator;
// 一共有多少列
@property(nonatomic,readonly) NSInteger numberOfComponents;
2.UIPickerView 的常见方法
// 重新刷新所有列
- (void)reloadAllComponents;
// 重新刷新第component 列
- (void)reloadComponent:(NSInteger)component;
// 主动选中第component 列的第row 行
- (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated;
// 获得第component 列的当前选中的行号
- (NSInteger)selectedRowInComponent:(NSInteger)component;
3. 数据源方法(UIPickerViewDataSource)
// 一共有多少列
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView;
// 第component 列一共有多少行
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component;
4. 代理方法(UIPickerViewDelegate)
// 第component 列的宽度是多少
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component;
// 第component 列的行高是多少
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component;
// 第component 列第row 行显示什么文字
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component;
// 第component 列第row 行显示怎样的view( 内容)
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view;
// 选中了pickerView 的第component 列第row 行
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component;
二.UIDatePicker
1.常见属性
// datePicker 的显示模式
@property (nonatomic) UIDatePickerMode datePickerMode;
// 显示的区域语言
@property (nonatomic, retain) NSLocale *locale;
2. 监听UIDatePicker 的选择
* 因为UIDatePicker 继承自UIControl, 所以通过addTarget:... 监听
2015/11/3
Day 31
今天学习程序启动原理
常见属性
◆ Localiztion native development region(CFBundleDevelopmentRegion) - 本地化相关
◆ Bundle display name(CFBundleDisplayName) - 程序安装后显示的名称, 限制在10 -12 个字符,如果超出,将被显示缩写名称
◆ Icon file(CFBundleIconFile) -app 图标名称, 一般为Icon.png
◆ Bundle version(CFBundleVersion) - 应用程序的版本号,每次往App Store 上发布一个新版本时,需要增加这个版本号
◆ Main storyboard file base name(NSMainStoryboardFile) - 主storyboard 文件的名称
◆ Bundle identifier(CFBundleIdentifier) - 项目的唯一标识,部署到真机时用到
在 Xcode6 之前,创建一个新工程 xcode 会在 Supporting files 文件夹下面自动创建一个 “ 工程名 -Prefix.pch” 文件,也是一个头文件, pch 头文件的内容能被项目中的其他所有源文件共享和访问。是一个预编译文件。
首先说一下 pch的作用:
1.存放一些全局的宏 (整个项目中都用得上的宏 )
2.用来包含一些全部的头文件 (整个项目中都用得上的头文件 )
3.能自动打开或者关闭日志输出功能
Xcode6以后就不能自动创建了,苹果为什么要这么做呢,原因可能是因为大家把大量的头文件和宏定义放到 pch里边,导致编译时间过长。苹果去掉他可能是要加快编译时间增加用户体验。虽然失去了编程的便利性。不得不佩服苹果的以用户为中心的思考方式
UIApplication
UIApplication 对象是应用程序的象征
每一个应用都有自己的 UIApplication对象,而且是单例的
通过[UIApplication sharedApplication] 可以获得这个单例对象
一个 iOS程序启动后创建的第一个对象就是 UIApplication对象
利用 UIApplication对象,能进行一些应用级别的操作
@property(nonatomic) NSInteger applicationIconBadgeNumber;
@property(nonatomic,getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
从 iOS7 开始,系统提供了 2 种管理状态栏的方式
- (UIStatusBarStyle)preferredStatusBarStyle;
- (BOOL)prefersStatusBarHidden;
UIApplication 有个功能十分强大的 openURL: 方法
- (BOOL)openURL:(NSURL*)url;
openURL: 方法的部分功能有
UIApplication *app = [UIApplication sharedApplication];
[app openURL:[NSURL URLWithString:@"tel://10086"]];
[app openURL:[NSURL URLWithString:@"sms://10086"]];
[app openURL:[NSURL URLWithString:@"mailto://12345@qq.com"]];
[app openURL:[NSURL URLWithString:@"http://ios.itcast.cn"]];
所有的移动操作系统都有个致命的缺点: app很容易受到打扰。比如一个来电或者锁屏会导致 app进入后台甚至被终止
还有很多其它类似的情况会导致 app受到干扰,在 app受到干扰时,会产生一些系统事件,这时 UIApplication会通知它的 delegate对象,让 delegate代理来处理这些系统事件
delegate可处理的事件包括:
➢ 应用程序的生命周期事件 (如程序启动和关闭 )
➢ 系统事件 (如来电 )
➢ 内存警告
➢ … …
IOS 程序启动过程
UIApplicationMain
main 函数中执行了一个UIApplicationMain 这个函数
int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
➢ argc 、argv :直接传递给UIApplicationMain 进行相关处理即可
➢ principalClassName :指定应用程序类名(app 的象征),该类必须是UIApplication( 或子类) 。如果为nil, 则用UIApplication 类作为默认值
➢ delegateClassName :指定应用程序的代理类,该类必须遵守UIApplicationDelegate 协议
UIApplicationMain 函数会根据principalClassName 创建UIApplication 对象,根据delegateClassName 创建一个delegate 对象,并将该delegate 对象赋值给UIApplication 对象中的delegate 属性
接着会建立应用程序的Main Runloop (事件循环),进行事件的处理( 首先会在程序完毕后调用delegate 对象的application:didFinishLaunchingWithOptions: 方法)
程序正常退出时UIApplicationMain 函数才返回
UIWindow
UIWindow 是一种特殊的UIView ,通常在一个app 中只会有一个UIWindow
iOS程序启动完毕后,创建的第一个视图控件就是 UIWindow,接着创建控制器的 view,最后将控制器的 view添加到 UIWindow上,于是控制器的 view就显示在屏幕上了
一个 iOS程序之所以能显示到屏幕上,完全是因为它有 UIWindow
也就说,没有 UIWindow,就看不见任何 UI界面
添加UIView 到UIWindow 中两种常见方式:
直接将view 添加到UIWindow 中,但并不会理会view 对应的UIViewController
自动将rootViewController 的view 添加到UIWindow 中,负责管理rootViewController 的生命周期
常用方法
让当前UIWindow 变成keyWindow (主窗口)
让当前UIWindow 变成keyWindow ,并显示出来
UIWindow 的获得
在本应用中打开的 UIWindow列表,这样就可以接触应用中的任何一个 UIView对象
(平时输入文字弹出的键盘,就处在一个新的 UIWindow中 )
用来接收键盘以及非触摸类的消息事件的 UIWindow,而且程序中每个时刻只能有一个 UIWindow是 keyWindow。如果某个 UIWindow内部的文本框不能输入文字,可能是因为这个 UIWindow不是 keyWindow
获得某个UIView 所在的UIWindow
四大对象的关系
程序启动的完整过程
1.main 函数
2.UIApplicationMain
* 创建UIApplication 对象
* 创建UIApplication 的delegate 对象
3.delegate 对象开始处理( 监听) 系统事件( 没有storyboard)
* 程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions: 方法
* 在application:didFinishLaunchingWithOptions: 中创建UIWindow
* 创建和设置UIWindow 的rootViewController
* 显示窗口
3. 根据Info.plist 获得最主要storyboard 的文件名, 加载最主要的storyboard( 有storyboard)
* 创建UIWindow
* 创建和设置UIWindow 的rootViewController
* 显示窗口
2015/11/4
Day 32
今天开始学习多控制器
控制器常见的创建方式有以下几种
ViewController *vc = [[ViewController alloc] init];
ViewController *vc = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
通过 storyboard 创建控制器
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Test" bundle:nil];
初始化 “初始控制器 ”(箭头所指的控制器)
MJViewController *mj = [storyboard instantiateInitialViewController];
MJViewController *mj = [storyboard instantiateViewControllerWithIdentifier:@”mj"];
控制器 view的创建
❖ 控制器的 view是延迟加载的:用到时再加载
❖ 可以用isViewLoaded 方法判断一个UIViewController 的view 是否已经被加载
❖ 控制器的view 加载完毕就会调用viewDidLoad 方法
一个 iOS的 app很少只由一个控制器组成 ,除非这个 app极其简单
当 app中有多个控制器的时候 ,我们就需要对这些控制器进行管理
有多个 view时,可以用一个大的 view去管理 1个或者多个小 view
控制器也是如此,用 1个控制器去管理其他多个控制器
比如,用一个控制器 A去管理 3个控制器 B、 C、 D
➢ 控制器 A被称为控制器 B、 C、 D的 “父控制器 ”
➢ 控制器 B、 C、 D的被称为控制器 A的 “子控制器 ”
➢
为了便于管理控制器, iOS提供了 2个比较特殊的控制器
➢ UINavigationController
➢ UITabBarController
❖ 利用 UINavigationController,可以轻松地管理多个控制器,轻松完成控制器之间的切换,典型例子就是系统自带的 “设置 ”应用
UINavigationController 的使用步骤
初始化UINavigationController
设置UIWindow 的rootViewController 为UINavigationController
根据具体情况,通过 push方法添加对应个数的子控制器
注意:xcode6 之后push 和modal 就被废弃了。只能用于ios8 之前
这两个方法被废弃了,我们需要找到合适的方法来代替,这时候我们发现 show 和 Present Modally 方法,这个一般可以满足使用要求。
@property(nonatomic,copy) NSArray *viewControllers;
@property(nonatomic,readonly) NSArray *childViewControllers;
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (UIViewController *)popViewControllerAnimated:(BOOL)animated;
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated;
导航栏的内容由栈顶控制器的 navigationItem属性决定
UINavigationItem 有以下属性影响着导航栏的内容
@property(nonatomic,retain) UIBarButtonItem *backBarButtonItem;
@property(nonatomic,retain) UIView *titleView;
@property(nonatomic,copy) NSString *title;
@property(nonatomic,retain) UIBarButtonItem *leftBarButtonItem;
@property(nonatomic,retain) UIBarButtonItem *rightBarButtonItem;
数据存取
iOS应用数据存储的常用方式
● XML 属性列表(plist )归档
● Preference( 偏好设置)
● NSKeyedArchiver 归档(NSCoding)
● SQLite
● Core Data
应用沙盒
● 每个 iOS应用都有自己的应用沙盒 (应用沙盒就是文件系统目录 ),与其他文件系统隔离。应用必须待在自己的沙盒里,其他应用不能访问该沙盒
● 应用沙盒的文件系统目录,如下图所示(假设应用的名称叫 Layer)
● 应用程序包: (上图中的 Layer)包含了所有的资源文件和可执行文件
● Documents :保存应用运行时生成的需要持久化的数据, iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录
●
● tmp :保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。 iTunes同步设备时不会备份该目录
●
● Library/Caches :保存应用运行时生成的需要持久化的数据, iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据
●
● Library/Preference :保存应用的所有偏好设置, iOS的 Settings(设置 )应用会在该目录中查找应用的设置信息。 iTunes同步设备时会备份该目录
应用沙盒目录的常见获取方式
NSString *home = NSHomeDirectory ();
NSString *documents = [home stringByAppendingPathComponent :@" Documents "];
// 不建议采用,因为新版本的操作系统可能会修改目录名
// NSUserDomainMask 代表从用户文件夹下找
// YES 代表展开路径中的波浪字符 “~”
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
// 在 iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素
NSString *documents = [array objectAtIndex:0];
tmp :NSString *tmp = NSTemporaryDirectory ();
Library/Caches :( 跟Documents 类似的2 种方法)
利用沙盒根目录拼接 ”Caches”字符串
利用NSSearchPathForDirectoriesInDomains 函数( 将函数的第2 个参数改为:NSCachesDirectory 即可)
Library/Preference :通过NSUserDefaults 类存取该目录下的设置信息
属性列表
● 属性列表是一种 XML格式的文件,拓展名为 plist
● 如果对象是NSString 、NSDictionary 、NSArray 、NSData 、NSNumber 等类型,就可以使用writeToFile:atomically: 方法直接将对象写到属性列表文件中
属性列表 - 归档 NSDictionary
// 将数据封装成字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@" 母鸡" forKey:@"name"];
[dict setObject:@"15013141314" forKey:@"phone"];
[dict setObject:@"27" forKey:@"age"];
// 将字典持久化到Documents/stu.plist 文件中
[dict writeToFile:path atomically:YES];
属性列表 - 恢复 NSDictionary
// 读取Documents/stu.plist 的内容,实例化NSDictionary
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@"name:%@", [dict objectForKey:@"name"]);
NSLog(@"phone:%@", [dict objectForKey:@"phone"]);
NSLog(@"age:%@", [dict objectForKey:@"age"]);
偏好设置
NSUserDefaults*defaults = [NSUserDefaults standardUserDefaults ];
[defaults setObject :@“yu3” forKey:@"username"];
[defaults setFloat :18.0f forKey:@"text_size"];
[defaults setBool :YES forKey:@"auto_login"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *username = [defaults stringForKey :@"username"];
float textSize = [defaults floatForKey :@"text_size"];
BOOL autoLogin = [defaults boolForKey :@"auto_login"];
[defaults synchornize];
NSKeyedArchiver
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用 encodeObject:forKey:方法归档实例变量
每次从文件中恢复 (解码 )对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用 decodeObject:forKey方法解码实例变量
NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];
[ NSKeyedArchiver archiveRootObject :array toFile :path];
NSArray *array = [ NSKeyedUnarchiver unarchiveObjectWithFile :path];
对象的归档解档
@interface Person : NSObject< NSCoding >
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float height;
@end
@implementation Person
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject :self.name forKey:@"name"];
[encoder encodeInt :self.age forKey:@"age"];
[encoder encodeFloat :self.height forKey:@"height"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.name = [decoder decodeObjectForKey :@"name"];
self.age = [decoder decodeIntForKey :@"age"];
self.height = [decoder decodeFloatForKey :@"height"];
return self;
}
- (void)dealloc {
[super dealloc];
[_name release];
}
@end
Person *person = [[[Person alloc] init] autorelease];
person.name = @“yu3”;
person.age = 22;
person.height = 1.75f;
[NSKeyedArchiver archiveRootObject:person toFile:path];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
[super encodeWithCode:encode];
确保继承的实例变量也能被编码,即也能被归档
self = [super initWithCoder:decoder];
确保继承的实例变量也能被解码,即也能被恢复
NSData
使用 archiveRootObject:toFile:方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么就要使用 NSData来进行归档对象
NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用 [NSMutableData data]创建可变数据空间
NSData- 归档 2 个 Person 对象到同一文件中
// 新建一块可变数据区
NSMutableData*data = [ NSMutableData data];
// 将数据区连接到一个NSKeyedArchiver 对象
NSKeyedArchiver*archiver = [[[ NSKeyedArchiver alloc] initForWritingWithMutableData :data] autorelease];
// 开始存档对象,存档的数据都会存储到 NSMutableData中
[archiver encodeObject :person1 forKey:@"person1"];
[archiver encodeObject :person2 forKey:@"person2"];
// 存档完毕 (一定要调用这个方法 )
[archiver finishEncoding ];
// 将存档的数据写入文件
[data writeToFile :path atomically:YES];
// 从文件中读取数据
NSData *data = [NSData dataWithContentsOfFile :path];
// 根据数据,解析成一个NSKeyedUnarchiver 对象
NSKeyedUnarchiver*unarchiver = [[ NSKeyedUnarchiver alloc] initForReadingWithData :data];
Person *person1 = [unarchiver decodeObjectForKey :@"person1"];
Person *person2 = [unarchiver decodeObjectForKey :@"person2"];
// 恢复完毕
[unarchiver finishDecoding ];
利用归档实现深复制
// 临时存储person1 的数据
NSData *data = [ NSKeyedArchiver archivedDataWithRootObject :person1];
// 解析data ,生成一个新的Person 对象
Student *person2 = [ NSKeyedUnarchiver unarchiveObjectWithData :data];
// 分别打印内存地址
NSLog(@"person1:0x%x", person1); // person1:0x7177a60
NSLog(@"person2:0x%x", person2); // person2:0x7177cf0
2015/11/5
Day 33
今天做了一个多控制器跳转的小项目
storyboard 设计如下
页面的搭建和之间的跳转都是比较简单的,主要练习了一下利用偏好设置来实现记住密码以及自动登录功能
首先在监听登录按钮点击方法下加入
// 利用preferences 存储数据
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.userNameText.text forKey:@"username"];
[defaults setObject:self.pwdText.text forKey:@"pwd"];
[defaults setBool:self.rememberPwdSwich.isOn forKey:@"rememberPwd"];
[defaults setBool:self.autoLoginSwich.isOn forKey:@"autoLogin"];
[defaults synchronize];
这样就将数据存到偏好设置中了
然后在主控制器的 viewDidLoad方法中加入
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.rememberPwdSwich.on = [defaults boolForKey:@"rememberPwd"];
self.autoLoginSwich.on = [defaults boolForKey:@"autoLogin"];
if (self.rememberPwdSwich.isOn) {
self.userNameText.text = [defaults stringForKey:@"username"];
self.pwdText.text = [defaults stringForKey:@"pwd"];
}
if (self.autoLoginSwich.isOn) {
[self loginBtnClick];
}