主要知识点:
一、
* UIPickerView, 点餐系统、 城市选择、 国旗选择
* UIDatePicker, 日期选择控件
* UIToolbar, 工具栏
二、 项目中常见的文件
* 项目结构介绍
* info.plist介绍
* pch介绍
* 自定义LOG
-------------------------------------------------
三、 UIApplication(应用程序对象)
* UIApplication常见属性
* 应用程序状态栏管理
* UIApplication 的 OpenURL方法
四、 UIApplicationDelegate(应用程序代理)
* UIApplicationDelegate常见方法
* 程序启动过程
================================案例1:点餐系统================================
0. 通过自动布局实现支持所有的竖屏。
1. 拖一个UIPickerView控件到控制器界面上。
2. 设置UIPickerView的数据源为当前的控制器(ViewController)。通过拖线或者代码实现。
3. 让ViewController遵守UIPickerView的数据源协议:UIPickerViewDataSource。
4. 实现数据源方法:
1> numberOfComponentsInPickerView: // 设置当前UIPickerView中共有几列
2> pickerView:numberOfRowsInComponent: // 设置每列中有几行
3> ** 注意: 数据源协议中只有两个方法:
@protocol UIPickerViewDataSource<NSObject>
@required
// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView;
// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component;
@end
4> 所以要想设置每列中每行显示的数据内容,需要使用代理。
5. 设置UIPickerView的代理为当前控制器(ViewController)。通过拖线或者代码实现。
6. 让ViewController遵守UIPickerView的代理协议:UIPickerViewDelegate。
7. 在ViewController中实现pickerView:titleForRow:方法,返回每行的数据内容。
8. UIPickerViewDelegate中的内容:
// 待补充, 请查看头文件
9. 读取foods.plist文件,将foods.plist文件中的内容显示到UIPickerView上。//采用懒加载。
10. 监听UIPickerView的选择事件,将当前选中项显示到指定的Label上。
** 通过遵守代理协议:UIPickerViewDelegate ,并实现pickerView:didSelectRow:inComponent:方法来监听。
11. 在pickerView:didSelectRow:inComponent:方法中获取当前选中的行的内容(根据索引从self.foods数组中获取数据)设置到对应的Label上。
12. 解决bug: 当第一次加载好后,Label中默认显示值的问题。
** 解决:
// 手动调用 pickerView:didSelectRow:inComponent: 方法, 来设置默认选中项
// 设置每列的第0行默认被选中。
for (int i = 0; i < self.foods.count; i++) {
[self pickerView:nil didSelectRow:0 inComponent:i];
}
13. 左上角放一个按钮,实现随机选餐。
** 13.1 设置UIPickerView主动选中某项。调用UIPickerView的selectRow:inComponent:animated:方法
** 13.2 通过调用C语言的arc4random()函数,生成一个随机的无符号正数,用该数字与某个正数取余来得到一个随机数。比如要得到一个0-11之间的随机数,那么就用 arc4random() % 12。也可以使用: arc4random_uniform(12), 意思一样。
/** 产生随机数的两种方法:
** 通过调用C语言函数实现。
1> arc4random()
* 生成一个随机的无符号正数,(0 ~ (2的32次方)-1)
* 用该数字与某个正数取余来得到一个随机数
* 比如要得到一个0-11之间的随机数,那么就用 arc4random() % 12
* 要生成一个0 - n 的随机数, 那么就用 arc4random() % (n + 1) = ?
参考:
// 定义一个要生成的随机数的上边界(不含)
int upper_bound = 10;
for (int i = 0; i < 100; i++) {
// 生成一个 0 ~ (upper_bound - 1) 的随机数
unsigned int rdn = arc4random() % upper_bound;
NSLog(@"%u", rdn);
}
2> arc4random_uniform() 推荐使用这个
* 直接使用arc4random_uniform(12), 得到一个0-11之间的随机数。
* 上面的写法等价于 arc4random % 12
*/
** 13.3 UIPickerView被选择后,修改下面对应的Label的文字。
** 调用UIPickerView的selectRow:inComponent:animated:只会改变UIPickerView的选择, 并不会调用pickerView:didSelectRow:inComponent:方法。
** 调用完毕上述方法后, 再手动调用一次代理的pickerView:didSelectRow:inComponent:方法来设置Lable上的显示文字。
** 13.4 防止本次生成的随机数, 与当前选择的项一致, 导致点餐没有变化。解决:每次获取新的随机数后,通过调用UIPickerView的selectedRowInComponent:方法获取当前component中被选中的行的索引,然后与本次生成的随机数比较如果相同,则再重新生成一个新的值。
/** 参考代码:
for (int i = 0; i < self.foods.count; i++) {
// 0. 获取当前列中的选择项的索引
NSUInteger currentIndex = [self.pickerViewFood selectedRowInComponent:i];
// 一开始假设两个值相同。
NSUInteger rdnIndex = currentIndex;
do {
// 1. 产生随机数
rdnIndex = arc4random() % [self.foods[i] count];
} while (rdnIndex == currentIndex);
// 设置执行项被选中。
[self.pickerViewFood selectRow:rdnIndex inComponent:i animated:YES];
[self pickerView:nil didSelectRow:rdnIndex inComponent:i];
}
*/
** 注意: 因为self.foods[index]等价于 [self.foods objectAtIndex:index], 而objectAtIndex方法返回值是id, id是不能使用"点儿语法"的。所以不能直接self.foods[index].count; 只能通过[self.foods[index] count]。(objectAtIndexedSubscript:index)
================================案例1:点餐系统================================
================================作业,利用UIPickerView实现省市联动选择效果================================
- (void)reloadAllComponents; // 重新加载所有数据
- (void)reloadComponent:(NSInteger)component; // 重新加载指定列的数据
上面两个方法表示重新刷新数据, 即: 重新调用数据源方法与代理方法。
// 重新加载数据的意思: 就是表示重新调用数据源方法, 以及那些和加载数据有关的代理方法。
/*
数据源方法和代理方法参考:
#pragma mark - ************* 数据源方法 *************
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
// 只有两列: 省、市
return 2;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
// 根据是第几列, 判断要返回多少条数据
if (component == 0) {
// 如果是第0列, 那么就返回所有的省份的个数
return self.areas.count;
} else {
// 如果是第1列, 那么就要根据当前第0列选中的行的索引, 在数据中找到对应的省份, 然后再把该省份的城市返回
// 1. 获取当前选中的省份的索引
int provinceIdx = [self.pickerViewArea selectedRowInComponent:0];
// 2. 返回对应省份的城市的个数
return [[self.areas[provinceIdx] cities] count];
}
}
#pragma mark - ************* 代理方法 *************
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
if (component == 0) {
// 如果是第0列, 表示要的是省份信息
// 1. 现根据 row, 获取所要的省份对象
// 2. 然后通过 name 属性获取该省份的名称
return [self.areas[row] name];
} else {
// 如果是第1列, 表示要获取的是当前选中的省份的城市信息
// 1. 获取当前选中的省份的索引(获取 picker view 中第0列的当前选中的索引)
int proIdx = [self.pickerViewArea selectedRowInComponent:0];
// 获取当前省份所辖的总的城市个数
int cityCount = [[self.areas[proIdx] cities] count];
if (row < cityCount) {
return [self.areas[proIdx] cities][row];
}
return nil;
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (component == 0) {
// 刷新第2列
[self.pickerViewArea reloadComponent:1];
// 默认让第二列的第0行被选中
[self.pickerViewArea selectRow:0 inComponent:1 animated:YES];
}
// 获取当前选中的项的内容显示到 Label 上
// 1. 获取当前选中的省份的索引
int proIdx = [self.pickerViewArea selectedRowInComponent:0];
int cityIdx = [self.pickerViewArea selectedRowInComponent:1];
self.lblProvince.text = [self.areas[proIdx] name];
self.lblCity.text = [self.areas[proIdx] cities][cityIdx];
}
*/
/** 省市选择中的一个 bug:
1. bug 再现: 当同时滚动"省"和"城市"的时候会有 bug。
2. bug 产生的原因:
1> 在 - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component 方法中根据当前选中的省份来更新城市列的数据。
2> 但是 - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component 方法在省份"滚动"的时候并不会触发, 只有当省份"滚动"结束后才会触发, 也就是说只有当"省份滚动完毕"后才会根据当前选择的省份来重新加载"城市"的数据。
3> 但是在- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component方法中要根据当前选中的省份获取城市列表数据, 然后再根据当前选择的是哪个城市, 返回城市的名称。在这其中就会用到[pickerView selectedRowInComponent:0]方法, 而[pickerView selectedRowInComponent:0]方法是在"省份滚动"的每时每刻都会执行, 也就是说在"省份滚动"的时候[pickerView selectedRowInComponent:0]方法会获取每次滚动过程中经过的每个省的 id, 但是对应的城市列表中的数据始终是原来的城市数据, 数据不匹配了, 就造成了bug 的出现。
3. bug 举例:
1> 假设现在选择的省份是"江西"省
2> 江西省下面有11个城市
3> 现在开始同时滚动"省份"和"城市"
4> 因为在"省份"没有滚动停止下来的时候, "城市"列表中始终显示的是"江西省下面的城市"
5> 但是在"城市列表"滚动的过程中会触发- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component方法被执行
6> 而在- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component方法中, 会调用[pickerView selectedRowInComponent:0]获取当前选中的省份的索引([pickerView selectedRowInComponent:0]方法可能获取当前正在滚动时, 被滚动到的每个省份的索引), 比如滚动到了"澳门", 澳门下的城市列表就一个, 所以获取的城市列表集合中的数据只有一个, 但是当前- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component方法中的参数 row 还是根据"江西省下面"的城市列表获取的, 所以就造成了数组的越界
4. bug 解决:
1> 在- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component方法中获取城市列表后, 保存到一个集合中
2> 在- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component方法中判断, 如果 component == 1那么就直接从刚才保存的集合中获取数据, 不要再重新根据第0列选中的值去加载城市列表的数据了。
4. 提示: 查看 didSelectRow 方法 与 titleForRow:方法的执行时机, 以及在titleForRow:方法中获取[pickerView selectedRowInComponent:0]方法看当前选中行。
*/
================================作业,利用UIPickerView实现省市联动选择效果================================
================================ 案例2:国旗选择 ================================
1. 拖拽一个UIPickerView, 同时设置数据源、代理为当前ViewController。
2. 让当前ViewController遵守UIPickerViewDataSource 、 UIPickerViewDelegate协议。
3. 拷贝flags.plist、国旗的图片到Supporting Files文件夹中。
4. 编写懒加载代码,将flags.plist中的内容加载到NSArray中。同时创建NationalFlag模型用来替换字典存储数据。(在NSArray中保存的是模型对象,不是NSDictionary对象。)
** 注意:在使用KVC的时候,必须保证Model类的属性名字与NSDictionary中的名字一致,如果因为不一致导致执行报异常,那么把名字修改过来以后,记得Product -> clean。
/**
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict]; // KVC
}
return self;
}
+ (instancetype)nationalFlagWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
*/
5. 设置当前UIPickerView只有一列(一个component),其中包含了国家名称、国旗(图片), 因为"国家名称"与"国旗"是一起滚动的。
6. 将每个国家的名字显示在UIPickerView中。
** 调用(NSString *)pickerView:titleForRow:forComponent: 设置每行显示的文字内容
** 调用(UIView *)pickerView:viewForRow:forComponent:reusingView: 设置每行显示的UIView
7. 创建一个xib文件,在xib中创建写一个UIView(修改UIView的size为FreeForm然后才能改大小。修改该UIView的width与屏幕一致。)。
* 创建自己的UIView类,设置xib中的UIView使用自定义的UIView类。
* 在该UIView类中, 通过把传递过来的Flag的model数据设置到对应的Label和ImageView上。
* 在自定义UIView中, 封装一个类方法, 这个类方法中, 通过加载xib文件, 创建一个UIView。
/**
+ (instancetype)nationalFlagWithXib
{
return [[[NSBundle mainBundle] loadNibNamed:@"SteveZFlagView" owner:nil options:nil] lastObject];
}
*/
7.1 优化(从 iOS7开始一直到现在 reusingView都一直是 null):
/**
下面的这个方法, 每当有一行出现在视野范围内就会调用一次, 调用频率很高, 性能不好。
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
** 注意: UIPickerView每一行都一样, 所以无需通过"标识"来缓存, 在该方法中已经传递过来了一个reusingView。
** 注意: 但在iOS 7 和 iOS 8 下无效。iOS7 和 iOS8下reusingView的地址是0x0。
*/
================================ 案例2:国旗选择 ================================
===============================案例3:时间选择。UIDatePicker=====================
0. 需求:文本框获得焦点,弹出日期选择的键盘。
1. 拽一个UIDatePicker控件。(默认是英文日期)
2. 修改成中文日期格式。(找UIDatePicker的Locale属性,设置成Chinese Simplified)
3. Mode属性来设置显示模式。
4. 创建一个文本框, 通过在ViewController中拖线,使用一个属性与UITextField相关联。
// 自定义文本框的弹出键盘
5. 通过设置UITextField的inputView属性来修改当文本框获得焦点后,弹出什么控件。设置该属性的值为UIDatePicker控件。(动态创建一个UIDatePicker控件(无需设置高宽)),这样就可以实现当文本框获得焦点后,自定义弹出键盘了。
/** 参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
// 创建UIDatePicker对象
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
// 设置语言区域
datePicker.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh-Hans"];
// 设置显示模式
datePicker.datePickerMode = UIDatePickerModeDateAndTime;
// 设置文本框, 当输出的时候显示的键盘是日期选择控件
self.txtDate.inputView = datePicker;
}
*/
6. 设置UIDatePicker的datepickerMode、Locale
【解释一下“区域”(数字、时间、货币等表示的格式和计量单位的区别, 区域和语言没有直接关系)和“语言”(只是语言)的区别。】
【日历:指"公历"、“日本日历”、“佛教日历”】
* picker.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh-Hans"];
* picker.datePickerMode = UIDatePickerModeDateAndTime;
** 当日期选择控件选择的日期改变后, 将新的日期设置到文本框内。
** 思路:为日期选择控件注册ValueChanged事件, 当该事件被触发时获取日期, 并显示到文本框中。
** 选中 UIDatePicker拖线 action到控制器, 查看默认的事件, 就是 ValueChanged。这个事件就是最常用的事件
** 拖一个UIDatePicker到View中, 然后拖线到控制器, 查看Action中的事件(最常用的就是Value Changed事件)
/** 参考代码:
[self.datePicker addTarget:self action:@selector(dpDateChanged) forControlEvents:UIControlEventValueChanged];
- (void)dpDateChanged
{
// 创建日期格式化器
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// 设置日期格式
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
// 格式化日期、
self.txtDate.text = [formatter stringFromDate:self.datePicker.date];
// 关闭键盘
[self.view endEditing:YES];
}
*/
// 自定义键盘上的工具控件
7. 设置文本框的inputAccessoryView属性。比如:txtField.inputAccessoryView = 某个UIView;
** 工具条的使用: UIToolbar, 演示设置工具条背景色、 设置背景色透明。(barTintColor,从iOS7开始;backgroundColor继承自UIView, 从iOS2开始。)
** 拖拽几个UIBarButtonItem到(UIToolbar)工具条上面, 注册事件, 测试。
** Accessory, 表示"附件"、"装饰品"的意思。
8. 注意:在UIToolbar中只能放UIBarButtonItem。(在工具箱中找BarButtonItem)。
** 如何设置BarButtonItem显示在最右边?在最右边的BarBtttonItem之前插入一个BarButtonItem,并设置该BarButtonItem的identifier的值为Flexible Space。
** 工具条一般用在键盘上面, 另外:safari下面那个也是ToolBar。
** 创建好工具条以后要设置工具条的 frame。
9. 通过代码动态创建一个UIToolbar, 并且将动态创建的UIToolbar设置给UITextField的inputAccessoryView属性。退出键盘的方式依然是:[self.view endEditing:YES]。
/** 设置键盘工具条的参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
// 创建UIDatePicker对象
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
// 设置语言区域
datePicker.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh-Hans"];
// 设置显示模式
datePicker.datePickerMode = UIDatePickerModeDateAndTime;
// 设置文本框, 当输出的时候显示的键盘是日期选择控件
self.txtDate.inputView = datePicker;
self.keyboardDatePicker = datePicker;
// 为日期控件注册一个值改变事件
[datePicker addTarget:self action:@selector(datepickerValueChanged) forControlEvents:UIControlEventValueChanged];
// ====================== 设置文本框弹出键盘时的工具条 ======================
// 1. 创建工具条
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
// 2.向工具条中增加一些按钮(UIBarButtonItem)
UIBarButtonItem *item1 = [[UIBarButtonItem alloc] initWithTitle:@"上一个" style:UIBarButtonItemStylePlain target:self action:@selector(previousClick:)];
UIBarButtonItem *item2 = [[UIBarButtonItem alloc] initWithTitle:@"下一个" style:UIBarButtonItemStylePlain target:self action:@selector(nextClick:)];
UIBarButtonItem *item3 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *item4 = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStylePlain target:self action:@selector(doneClick:)];
// 将按钮添加到toolBar中
toolBar.items = @[item1, item2, item3, item4];
// 设置工具条到文本框的inputAccessoryView属性
self.txtDate.inputAccessoryView = toolBar;
// ====================== 设置文本框弹出键盘时的工具条 ======================
}
*/
==================================程序的启动原理==================================
一、 项目中的常见文件
** XxxxTest文件夹用来做单元测试。
** Products目录。
* 在Mac程序下,生产的可执行文件app会存放到该目录下。
* iOS程序产生的是ipa文件。这个目录对于iOS程序来说意义不大。
1. info.plist(全局配置文件, 非常重要, 不能删除 ):
* 在xcode5 中是"项目名称-Info.plist"表示项目的全局配置文件,非常重要。在旧版本xcode中(和xcode6.1),该文件名称就叫Info.plist。
** 注意:自己创建的plist文件中不要包含Info关键字。
* Info.plist中的一些配置项:
1> Bundle display name(在xcode6.1中叫做"Bundle name"), 表示软件安装到手机上后,显示的名称。
** 当修改了该名称后,为了保证有效, 点击Product -> Clean, 同时将软件从模拟器中卸载掉,然后再重新运行。
2> Bundle identifier, app的唯一标识。
3> Bundle version, 每次发布软件的版本号。每次向AppStore上传的同一个软件, 新的版本号必须大于旧的, 否则无法上传。
4> Main storyboard file base name, 对应的就是选中"项目" -> "General" -> "Deployment Info" -> "Main Interface" 中的设置。
5> Supported interface orientations, 标识设备所支持的方向。对应的选中"项目" -> "General" -> "Deployment Info" -> "Device Orientation"。iPhone只支持三种方向, 不支持上下旋转(iPad支持)。Portrait(竖屏)、Landscape Left(横屏向左)、Landscape Right(横屏向右)。
6> Info.plist就是一个xml文件, 用记事本打开看一下。
2. pch文件:(Prefix Header File)(头文件)
* 遇到的问题:
1> 整个项目中很多地方都在使用某个类的头文件。
2> 整个项目中很多地方都在使用同一个"宏"
3> 在项目中很多地方用到了NSLog()函数, 想一下子全都清除掉。
* 解决上面的问题, 可以通过使用PCH文件(Prefix Header File)。
* pch文件就是一个头文件(类似于*.h文件)。
** 注意: PCH文件的特点, 项目中的所有其他代码文件无需显示导入该PCH文件, 默认就都可以访问(其他文件无需手动#import该 pch文件就能使用)。
/**
参考代码:
// 假设在整个项目中有多个地方在使用Person类, 那么可以在每个使用到Person类的地方都#import "Person.h", 或者把#import "Person.h"放到pch文件中。
*/
* 主要作用:
1> 可以放一些公用的宏定义。
2> 把公共的Model类的#import导入写到pch文件中。
3> 自定义NSLog()。例如: #define SteveZLog(...) NSLog(__VA_ARGS__)
** 遇到的问题: 在项目中很多地方用到了NSLog()函数, 想一下子全都清除掉。
/** 参考代码:
int age = 20;
NSString *name = @"steve";
NSLog(@"name: %@, age: %d",name, age);
// 在项目中的很多地方都用到了NSLog()
NSLog(@"------------------------");
// 在pch文件中定义如下宏:
// ... 表示SteveZLog可以接受多个参数
// __VA_ARGS__ 表示把用户的 ... 参数传递给NSLog. (VA 表示Value, ARGS表示arguments)
#define SteveZLog(...) NSLog(__VA_ARGS__)
// ===============然后就可以将所有的NSLog替换为SteveZLog了======================
// 当在项目中不再需要NSLog()的时候, 只要将SteveZLog中对应的代码注释掉即可:
参考代码:
#define SteveZLog(...) //NSLog(__VA_ARGS__)
*/
* 在xcode6.1中, 默认没有创建pch文件。
** 需要自己新建一个
** 创建方式: 选择"Supporting Files" -> 右键 -> "New File" -> "Other" -> "PCH File" -> "PrefixHeader.pch"。
* 在该文件中定义如下宏:
** #define ABC 10
* 选中项目 -> Build Setting -> All -> 搜索"prefix head" -> 修改Prefix Header的内容为:
** "$(SRCROOT)/$(PRODUCT_NAME)/PrefixHeader.pch" (如果有问题,换下面的方式,可能会与中文有关)
** 或者
** "$(SRCROOT)/对应的文件夹名/PrefixHeader.pch"
** 参考连接: http://www.cnblogs.com/YouXianMing/p/3989155.html
/** 参考代码:
pch文件代码:
#define ABC 10
#define Name @"赵晓虎-Steve"
#import "Person.h"
====================================
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%d", ABC);
NSLog(@"%@", Name);
// ----------------------
Person *p = [[Person alloc] init];
p.name = @"steve";
NSLog(@"%@", p.name);
}
*/
/*
补充, 程序开发阶段分为:
1. 调试阶段, 写代码、调错误,需要使用NSLog()。同时在调试阶段系统会自定义一个叫做DEBUG的宏。
2. 发布阶段, 写好的代码生成ipa等压缩文件, 上传到AppStore, 安装到用户设备上, 不需要NSLog()。 同时系同会自动删除叫做DEBUG的宏。
3. 综上所述, 我们可以如下自定义一个NSLog():
#ifdef DEBUG
#define SteveZLog(...) NSLog(__VA_ARGS__)
#else
#define SteveZLog(...)
#endif
4. 所有与OC相关的宏定义都要写在:
#ifdef __OBJC__
#endif
里面, 因为写在这个if中的所有内容只有OC代码才可以使用, 当在项目中新建了一个.c文件是不可以使用的(不使用就不会报错), 否则如果写在了外面,新建一个.c的程序也能使用,就报错了。比如C语言中没有#import, 只有#include <xx.h>
参考代码:
#ifndef testPCH_PrefixHeader_pch
#define testPCH_PrefixHeader_pch
// Include any system framework and library headers here that should be included in all compilation units.
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
// 判断如果是OC文件才引入这些宏, 如果是普通C语言文件则不引入。否则当在项目中添加C语言文件时报错。
#ifdef __OBJC__
#define ABC 10
#define Name @"赵晓虎-Steve"
#import "Person.h"
#ifdef DEBUG
#define SteveZLog(...) NSLog(__VA_ARGS__)
#else
#define SteveZLog(...)
#endif
#endif
*/
3. UIApplication对象介绍:
1> 一个UIApplication代表是一个应用程序,而且是单例的。
** 用来封装整个应用程序的一个对象, 比如当应用程序执行到某个时期要做什么, 生命周期等。
2> 获取UIApplication对象: [UIApplication sharedApplication];(单例的)
/** 验证单例模式, 参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
UIApplication *app1 = [UIApplication sharedApplication];
UIApplication *app2 = [UIApplication sharedApplication];
// 直接 [[UIApplication alloc] init]会报异常。
NSLog(@"%p----%p",app1, app2);
}
*/
3> 当一个iOS程序启动后,首先创建的第一个对象就是UIApplication对象。
4> 利用UIApplication可以做一些应用级别的操作。
* 应用级别操作:
1> QQ有消息的时候右上角的消息条数。
// 获取UIApplication对象。
UIApplication *app = [UIApplication sharedApplication];
// 设置右上角, 有10条消息
app.applicationIconBadgeNumber = 10;
// 取消显示消息
app.applicationIconBadgeNumber = 0;
/** 参考代码:
// 当点击按钮时, 设置右上角消息
- (IBAction)click:(id)sender {
// 获取UIApplication对象
UIApplication *app = [UIApplication sharedApplication];
// iOS 8 系统要求设置通知的时候必须经过用户许可。
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil];
[app registerUserNotificationSettings:settings];
app.applicationIconBadgeNumber = 10; // 有10条消息
// app.applicationIconBadgeNumber = app.applicationIconBadgeNumber > 0 ? 0 : 10; // 有10条消息
}
*/
2> 联网操作时,状态栏上的等待图标指示器。waiting图标。
UIApplication *app = [UIApplication sharedApplication];
app.networkActivityIndicatorVisible = YES;
3> 利用UIApplication打开某个资源:
** 系统会自动根据协议识别使用某个app打开。
UIApplication *app = [UIApplication sharedApplication];
** 打开一个网页:
[app openURL:[NSURL URLWithString:@"http://ios.icast.cn"]];
** 打电话
[app openURL:[NSURL URLWithString:@"tel://10086"]];
** 发短信
[app openURL:[NSURL URLWithString:@"sms://10086"]];
** 发邮件
[app openURL:[NSURL URLWithString:@"mailto://12345@qq.com"]];
** 使用openURL方法也可以打开其他应用,在不同应用之间互相调用对方。
** 美图秀秀, 点击分享到"新浪微博", 打开"新浪微博"选择账号, 跳转回"美图秀秀", 开始分享
** 喜马拉雅, 使用微博、QQ 账号 登录。都需要应用程序间跳转。
4> 通过UIApplication管理状态栏:
** 自从iOS7开始可以通过两种方式来控制状态栏
1> 控制器
* 通过UIViewController管理(每一个UIViewController都可以拥有自己不同的状态栏)
* 需要在控制器中实现如下方法:
/** 参考代码:
// 是否隐藏"状态栏"
- (BOOL)prefersStatusBarHidden
{
return NO;
}
// 状态栏的样式
- (UIStatusBarStyle)preferredStatusBarStyle
{
// 白色
return UIStatusBarStyleLightContent;
}
*/
2> UIApplication
* 通过UIApplication管理(一个应用程序的状态栏都由它统一管理)
** iOS7开始状态栏默认交给了控制器来管理,如果希望通过UIApplication来管理,步骤如下:
1> 在Info.plist文件中增加一个配置项
* View controller-based status bar appearance = NO,
2>然后编写如下代码:
/** 参考代码:
- (IBAction)click:(id)sender {
UIApplication *app = [UIApplication sharedApplication];
// 设置状态栏是否隐藏
//app.statusBarHidden = YES;
// 动画的方式
//[app setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
// 设置状态栏显示为白色
// app.statusBarStyle = UIStatusBarStyleLightContent;
// 动画的方式
//[app setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
}
*/
5. UIApplicationDelegate介绍。
** 新建完项目以后的那个AppDelegate文件, 就是UIApplication的代理对象。
* 并且该代理对象已经被设置好了, 无需我们手动设置了。
* 在main函数中进行的设置
/**
int main(int argc, char * argv[]) {
@autoreleasepool {
// 设置启动UIApplication对象, 和对应的代理对象AppDelegate。
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
*/
** App容易受到干扰。正在玩游戏,一个电话打过来了。
* 应用程序的生命周期事件(如程序启动和关闭)
* 系统事件(如来电)
* 内存警告
* … …
** 处理这些干扰事件,就要用到AppDelegate代理对象了。
** 总结: AppDelegate的主要作用就是处理(监听)应用程序本身的各种事件:
* 应用程序启动完毕
* 应用程序进入后台
* 应用程序进入前台
* 内存警告
* 等等, 都是应用程序自身的一些事件
** 要想成为UIApplication的代理对象, 必须遵守:UIApplicationDelegate协议。
** 代理中的若干方法介绍:
1. - (BOOL)application: didFinishLaunchingWithOptions:
// app第一次启动完毕后就会调用(当程序启动后会显示一张启动图片, 当这个图片显示完毕, 消失后, 就开始调用这个方法)
2. - (void)applicationDidEnterBackground:(UIApplication *)application
// 当程序进入后台时, 调用该方法。(比如:按了Home键, 或者一个电话打过来了, 当前程序都会进入后台。)
// 在这个方法中可以做一些保存当前程序数据, 暂停程序的操作。
3. - (void)applicationWillEnterForeground:(UIApplication *)application
// 当程序再次进入前台的时候调用。
4. - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
// 当发生内存警告时触发该事件。
6. UIApplicationMain函数介绍。
/**
方法名:
int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
参数介绍:
argc:使用main函数的argc即可
argv:使用main函数的argv即可
principalClassName:指定应用程序类名(app的象征),该类必须是UIApplication(或子类),如果为nil,则用UIApplication类作为默认值
delegateClassName:指定应用程序的代理类,UIApplicationDelegate协议中定义的方法,在该类中实现.
UIApplicationMain函数会:
1> 根据principalClassName创建UIApplication对象
2> 根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性
程序正常退出时UIApplicationMain函数才返回
默认调用方式:
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
等价于
UIApplicationMain(argc, argv, @"UIApplication", @"AppDelegate");
** 代理参数必须传递, 如果传nil, 则显示"黑屏"。
*/
7. iOS程序启动过程。参考PPT上的图片。
1> 打开程序。
2> 调用main函数。
3> 在main函数中调用: UIApplicationMain()函数。
* 在UIApplicationMain()函数中:
1. 创建UIApplication对象
2. 创建AppDelegate代理对象
3. 将AppDelegate代理对象设置给UIApplication对象。
4. 在UIApplicationMain()函数开启一个"死循环(事件循环)", 所以程序不会退出, 我们可以任意使用。在这个"死循环(事件循环)"中程序不断监听用户的各种事件, 依次处理(依靠"事件队列"实现)。
5. 程序启动完毕: 触发application:didFinishLaunchingWithOptions事件。
4> 程序退出。
8. UIWindow对象。
** UIWindow是一种特殊的UIView, UIWindow也是继承自UIView。
** 通常一个app只会有一个UIWindow对象。
** iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了
** 一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow
** 在文档中找: Cocoa Touch Layer -> UIKit -> Guides -> View Controller Programming Guide for iOS -> View Controller Basics -> 关于UIWindow与控制器View的关系图片。
** 创建过程UIWindow -> UIViewController -> UIView -> 把UIView加到UIWindow对象中。
================== 演示在一个空项目中创建界面内容 ========================
** xcode6.1 中没有创建空项目的模板, 所以可以新建一个Single View Application, 然后删除Main.storyboard 、 默认的ViewController、 同时设置Main Interface为空。然后运行, "黑屏"。因为没有UIWindow, 就没有界面。
** 开始一步一步创建界面:
1> 在application: didFinishLaunchingWithOptions:方法中创建UIWindow对象。
/** 参考代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建一个UIWindow对象
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 设置UIWindow背景色
self.window.backgroundColor = [UIColor blueColor];
// 显示UIWindow
[self.window makeKeyAndVisible];
return YES;
}
*/
2> 新建一个自己的控制器类, 并在该控制的viewDidLoad事件中, 设置控制器的view的背景色为红色。
/** 参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
// 设置控制器中的view的背景色为"红色"
self.view.backgroundColor = [UIColor redColor];
}
*/
3> 将控制器的view添加到UIWindow中。
** 方式一: 直接把控制器的View添加到UIWindow中。(不推荐)
/** 参考代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建一个UIWindow对象
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 设置UIWindow背景色
self.window.backgroundColor = [UIColor blueColor];
//-----------------------------------------------------------------
// 创建自定义的控制器, 并将控制器的view加到UIWindow中。
SteveZViewController *vc1 = [[SteveZViewController alloc] init];
[self.window addSubview:vc1.view];
//-----------------------------------------------------------------
// 显示UIWindow
[self.window makeKeyAndVisible];
return YES;
}
** 使用这种方式的缺点:
//1> 当该方法执行完毕控制器就释放了, 但是控制器的view还在
2> 当手机旋转的时候, 界面中的内容不会跟着旋转。
3> UIView中的各种子控件的事件监听程序都写在了一个类中, 无法分为多个控制器来管理。
*/
** 方式二: 通过设置UIWindow的根控制器的方式(推荐)
/** 参考代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建一个UIWindow对象
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 设置UIWindow背景色
self.window.backgroundColor = [UIColor blueColor];
//-----------------------------------------------------------------
// 创建自定义的控制器, 并将控制器的view加到UIWindow中。
SteveZViewController *vc1 = [[SteveZViewController alloc] init];
self.window.rootViewController = vc1;
//-----------------------------------------------------------------
// 显示UIWindow
[self.window makeKeyAndVisible];
return YES;
}
*/
** 思考: 如果不使用控制器, 直接把控件添加到UIWindow中行不行?
答: 可以。 但不推荐。如果把所有控件都直接加到UIWindow上, 那么很多个界面, 每个界面的各种事件监听都交给了同一个类来处理了。
** 总结: UIWindow就一个作用"显示界面", 将来是通过切换不同的控制器View分别显示到UIWindow上。
** 复习通过storyboard方式启动项目的方式:
1> 新建一个Single View Application, 观察AppDelegate中的代码, 虽然有application: didFinishLaunchingWithOptions:方法, 但是并没有在该方法中创建任何UIWindow。
2> 带Main.storyboard的启动方式:
1. 启动App
3. 创建UIWindow对象。在该方法中系统已经生成了创建UIWindow的方法, 不需要我们手动编写。
4. 根据Info.plist文件配置(Main Interface),找到需要加载的storyboard文件(Main.storyboard)
5. 找到Main.storyboard中的Is Initial View Controller 对应的控制器类, 创建该控制器对象。
6. 根据storyboard中的配置, 创建控制器对应的view。
7. 设置UIWindow的根控制器(rootViewController)为刚才创建的控制器。
调用AppDelegate的 application: didFinishLaunchingWithOptions:方法
8. 显示UIWindow([self.window makeKeyAndVisible])。
9. 注意, 当删除了Main Interface 中的配置后, 在运行程序会看到“黑屏”, 因为没有创建"控制器", 也没有设置UIWindow的rootViewController。
** 总结程序启动完整过程。(有storyboard和没有storyboard的分别总结)
一、 没有storyboard文件
1. 调用main函数。
2. 调用UIApplicationMain函数。
3. 创建UIApplication对象 、 AppDelegate对象
4. 设置UIApplicatio对象的代理是AppDelegate对象。
5. AppDelegate对象开始监听"系统事件(应用程序的事件)",进入"事件循环"。
6. 程序启动完毕后调用 application: didFinishLaunchingWithOptions:方法。
7. 在application: didFinishLaunchingWithOptions:方法中创建:
* UIWindow
* 控制器
* 设置UIWindow的根控制器是刚才创建的控制器
* 显示UIWindow
二、 有storyboard文件
1. 调用main函数。
2. 调用UIApplicationMain函数。
3. 创建UIApplication对象 、 AppDelegate对象
4. 设置UIApplicatio对象的代理是AppDelegate对象。
5. AppDelegate对象开始监听"系统事件(应用程序的事件)",进入"事件循环"
6. 程序启动完毕后调用 application: didFinishLaunchingWithOptions:方法。
7. 在application: didFinishLaunchingWithOptions:方法中创建:
* 系统自动创建UIWindow对象。
* 根据Info.plist文件配置(Main Interface),找到需要加载的storyboard文件(Main.storyboard)
* 找到Main.storyboard中的Is Initial View Controller 对应的控制器类, 创建该控制器对象。
* 根据storyboard中的配置, 创建控制器对应的view。
* 设置UIWindow的根控制器(rootViewController)为刚才创建的控制器。
* 显示UIWindow([self.window makeKeyAndVisible])。
*** 总结:UIApplication(应用程序对象) 、 AppDelegate(应用程序代理对象) 、 UIWindow(窗口对象) 、 ViewController(控制器对象)四个对象的关系。
*** 注意:要让设计器中看到的与最终手机屏幕上看到的一致,则把设计器中的ViewController的大小调整到与手机模拟器大小一致了。否则可能会出现在设计器中看到的与模拟器中运行的结果不一致问题。
*** 注意:在xcode帮助文档中查看iOS7 和 iOS6中控件的不同展示方式:iOS8.1 -> User Experience -> Guides -> iOS 7 UI Transition Guide, 然后打开右侧的子菜单。