一个java程序员自学IOS开发之路(七)
一个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
今天学习程序启动原理
- 建立一个工程后,会在Supporting files文件夹下看到一个“工程名-Info.plist”的文件,该文件对工程做一些运行期的配置,非常重要,不能删除
- 在现在Xcode7创建的工程中,这个配置文件的名字就叫“Info.plist”
- 项目中其他Plist文件不能带有“Info”这个字眼,不然可能会被错认为是传说中非常重要的“Info.plist”
常见属性
◆ 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种管理状态栏的方式
- 通过UIViewController管理(每一个UIViewController都可以拥有自己不同的状态栏)
- 通过UIApplication管理(一个应用程序的状态栏都由它统一管理)
- 在iOS7中,默认情况下,状态栏都是由UIViewController管理的,UIViewController实现下列方法就可以轻松管理状态栏的可见性和样式
- 状态栏的样式
- (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受到干扰,在app受到干扰时,会产生一些系统事件,这时UIApplication会通知它的delegate对象,让delegate代理来处理这些系统事件
delegate可处理的事件包括:
➢ 应用程序的生命周期事件(如程序启动和关闭)
➢ 系统事件(如来电)
➢ 内存警告
➢ … …
- 每次新建完项目,都有个带有“AppDelegate”字眼的类,它就是UIApplication的代理
- AppDelegate默认已经遵守了UIApplicationDelegate协议,已经是UIApplication的代理
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中两种常见方式:
- - (void)addSubview:(UIView *)view;
直接将view添加到UIWindow中,但并不会理会view对应的UIViewController
- @property(nonatomic,retain) UIViewController *rootViewController;
自动将rootViewController的view添加到UIWindow中,负责管理rootViewController的生命周期
常用方法
- - (void)makeKeyWindow;
让当前UIWindow变成keyWindow(主窗口)
- - (void)makeKeyAndVisible;
让当前UIWindow变成keyWindow,并显示出来
UIWindow的获得
- [UIApplication sharedApplication].windows
在本应用中打开的UIWindow列表,这样就可以接触应用中的任何一个UIView对象
(平时输入文字弹出的键盘,就处在一个新的UIWindow中)
- [UIApplication sharedApplication].keyWindow
用来接收键盘以及非触摸类的消息事件的UIWindow,而且程序中每个时刻只能有一个UIWindow是keyWindow。如果某个UIWindow内部的文本框不能输入文字,可能是因为这个UIWindow不是keyWindow
- view.window
获得某个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
今天开始学习多控制器
控制器常见的创建方式有以下几种
- 通过storyboard创建
- 直接创建
ViewController *vc = [[ViewController alloc] init];
- 指定xib文件来创建
ViewController *vc = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
通过storyboard创建控制器
- 先加载storyboard文件(Test是storyboard的文件名)
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Test" bundle:nil];
- 接着初始化storyboard中的控制器
初始化“初始控制器”(箭头所指的控制器)
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 方法,这个一般可以满足使用要求。
- UINavigationController以栈的形式保存子控制器
@property(nonatomic,copy) NSArray *viewControllers;
@property(nonatomic,readonly) NSArray *childViewControllers;
- 使用push方法能将某个控制器压入栈
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
- 使用pop方法可以移除控制器
- 将栈顶的控制器移除
- (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;
- UIBarButtonItem *rightBarButtonItem 右上角的视图
@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();
- Documents:(2种方式)
- 利用沙盒根目录拼接”Documents”字符串
NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@"Documents"];
// 不建议采用,因为新版本的操作系统可能会修改目录名
- 利用NSSearchPathForDirectoriesInDomains函数
// 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
- 将一个NSDictionary对象归档到一个plist属性列表中
// 将数据封装成字典
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
- 读取属性列表,恢复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"]);
偏好设置
- 很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能
- 每个应用都有个NSUserDefaults实例,通过它来存取偏好设置
- 比如,保存用户名、字体大小、是否自动登录
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"];
- 注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入
[defaults synchornize];
NSKeyedArchiver
- 如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复
- 不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以
- NSCoding协议有2个方法:
- encodeWithCoder:
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
- initWithCoder:
每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量
- 归档一个NSArray对象到Documents/array.archive
NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];
[NSKeyedArchiver archiveRootObject:array toFile:path];
- 恢复(解码)NSArray对象
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];
- 如果父类也遵守了NSCoding协议,请注意:
- 应该在encodeWithCoder:方法中加上一句
[super encodeWithCode:encode];
确保继承的实例变量也能被编码,即也能被归档
- 应该在initWithCoder:方法中加上一句
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];
利用归档实现深复制
- 比如对一个Person对象进行深复制
// 临时存储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];
}