一个java程序员自学IOS开发之路(七)

时间:2023-02-12 20:25:52

一个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”

 一个java程序员自学IOS开发之路(七)

常见属性

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可处理的事件包括:

应用程序的生命周期事件(如程序启动和关闭)

系统事件(如来电)

内存警告

… …

 一个java程序员自学IOS开发之路(七)

  • 每次新建完项目,都有个带有“AppDelegate”字眼的类,它就是UIApplication的代理

 

  • AppDelegate默认已经遵守了UIApplicationDelegate协议,已经是UIApplication的代理

IOS程序启动过程

 一个java程序员自学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

 

四大对象的关系

 一个java程序员自学IOS开发之路(七)

程序启动的完整过程

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的创建

 一个java程序员自学IOS开发之路(七) 

控制器的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,可以轻松地管理多个控制器,轻松完成控制器之间的切换,典型例子就是系统自带的“设置”应用

 一个java程序员自学IOS开发之路(七)

UINavigationController的使用步骤

初始化UINavigationController

设置UIWindow的rootViewController为UINavigationController

根据具体情况,通过push方法添加对应个数的子控制器

注意:xcode6 之后push 和modal 就被废弃了。只能用于ios8之前

一个java程序员自学IOS开发之路(七)

这两个方法被废弃了,我们需要找到合适的方法来代替,这时候我们发现 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)

 一个java程序员自学IOS开发之路(七)

应用程序包:(上图中的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-归档2Person对象到同一文件中

  • 归档(编码)

// 新建一块可变数据区

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设计如下

 一个java程序员自学IOS开发之路(七)

页面的搭建和之间的跳转都是比较简单的,主要练习了一下利用偏好设置来实现记住密码以及自动登录功能 

首先在监听登录按钮点击方法下加入

  //利用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];

    }