035-多控制器管理(1)-iOS笔记

/ 0

学习目标

1.【掌握】UIPickerView简介

2.【理解】UIPickerView显示字符串

3.【理解】UIPickerView显示自定义View

4.【掌握】UIDatePicker简介

5.【掌握】UIPickerView基本使用方法

6.【了解】项目中的常见文件

7.【理解】App启动原理

一、UIPickerView简介

UIPickerView也是一个iOS开发中常用的控件,适用于让用户选择少量数据,比如设置出生日期、城市、国家等。UIPickerView在iOS6和iOS7后的风格有所改变,之前是拟物化,现在变为扁平化了。他的用法和我们之前学习的UITableView类似,都需要用到数据源和代理来显示数据和监听事件。

//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;

UIPickerView的注意点
1.在ios8之前,UIPickerView的高度是(162)固定的,不能被修改。在 ios8后,它的高度就可以修改了。

2.下面这个方法的重用 view 在 ios6之后就一直为空。如果需要适配 ios6之前的系统,为了性能考虑,应该将这个 view 利用起来。

//重用View在iOS6之前才有效
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view;

pickerView的高度设置

如果没有主动设置高度,或者设置为0。则高度默认值216

如果设置的高度大于0且小于180,则高度默认值162

如果设置的高度大于等于180且小于216,怎么高度默认值为180

pickerView 的宽度设置

初始化时没有给定宽度,或者给定的宽度为0,则宽度默认等于屏幕的宽度。

给定了一个不为大于0的宽度,则宽度就等于给定的值。

二、UIPickerView显示字符串

UIPickerView可以显示纯文字,也能显示自定义View。我们用一个简单的案例来演示UIPickerView的基本使用方法,需求是实现滚动选择数据,并将选中的数据赋值给指定的控件,并提供一个随机选择的方法。最终效果图如下所示:

6

首先我们直接在Main.storyboard中搭建界面,由于只是演示,我就只做了UIPickerView的适配。

Snip20150829_22

我们将选择器中所要展示的数据存储在plist文件中了,并且没有用到字典,所以无需封装模型直接先懒加载数据存储到一个数组中即可。

Snip20150829_21

ViewController.m

#import "ViewController.h"

@interface ViewController () <UIPickerViewDataSource,UIPickerViewDelegate>

//UIPickerView控件
@property (weak, nonatomic) IBOutlet UIPickerView *pickerView;

//用于显示数据的Label
@property (weak, nonatomic) IBOutlet UILabel *categoriesLbl;
@property (weak, nonatomic) IBOutlet UILabel *timeLbl;
@property (weak, nonatomic) IBOutlet UILabel *constellationLbl;

//存储从plist加载进来的数据的数组
@property (strong, nonatomic) NSArray *filterArray;

//随机选择数据的方法
- (IBAction)randomSelect;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //设置UIPickerView的数据源、代理
    self.pickerView.dataSource = self;
    self.pickerView.delegate = self;
    
    //默认选中每列的第一行
    for (NSInteger component = 0; component < self.filterArray.count; component++) {
        [self pickerView:nil didSelectRow:0 inComponent:component];
    }
}

//懒加载
-(NSArray *)filterArray {
    if (!_filterArray) {
        //从plist文件中加载数据
        _filterArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"filter.plist" ofType:nil]];
    }
    return _filterArray;
}

//随机选择
- (IBAction)randomSelect {
    
    for (NSInteger component = 0; component < self.filterArray.count; component++) {
        
        //取出第component列的子数组的count
        NSInteger count = [self.filterArray[component] count];
        
        //返回第component列中选中的行
        NSInteger currentSelectedRow = [self.pickerView selectedRowInComponent:component];
        
        //假设随机生成的行和当前行相等
        NSInteger randomRow = currentSelectedRow;
        
        //随机生成一个和当前选中行不同的行号
        if (randomRow == currentSelectedRow) {
            randomRow = arc4random_uniform((u_int32_t)count);
        }
        
        //主动选中数据
        [self.pickerView selectRow:randomRow inComponent:component animated:YES];
        
        //手动调用这个方法,将选中的数据赋值给Label
        [self pickerView:self.pickerView didSelectRow:randomRow inComponent:component];
        
    }
}

//返回一共多少列
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return self.filterArray.count;
}

//返回component列的行数
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    return [self.filterArray[component] count];
}

//返回component列的row行的title
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
    return self.filterArray[component][row];
}

//当选中component列的row行时调用
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    
    NSArray *date = self.filterArray[component];
    NSString *title = date[row];
    
    //将选中的数据赋值给对应的Label
    if (component == 0) {
        self.categoriesLbl.text = title;
    } else if (component == 1) {
        self.timeLbl.text = title;
    } else if (component == 2) {
        self.constellationLbl.text = title;
    }
}

@end

 三、UIPickerView显示自定义View

用一个国旗选择的案例来演示如何让UIPickerView显示的数据是自定义View而不是纯字符串,其实和上面的案例区别不大,只是换了一个返回每列、每行数据如何显示的代理方法。上面案例是直接返回字符串,这里我们需要返回一个自定义View而已。下图是我们的需求:

7

首先创建项目导入plist文件,数据是以字典形式存储的,所以我们需要封装模型。

Snip20150829_23

JFFlag.h

#import <Foundation/Foundation.h>

@interface JFFlag : NSObject
//国旗名称
@property (copy, nonatomic) NSString *name;

//国旗图片
@property (copy, nonatomic) NSString *icon;

//快速创建模型
- (instancetype)initWithDictionary:(NSDictionary *)dict;
+ (instancetype)flagWithDictyinary:(NSDictionary *)dict;

//返回模型数组
+ (NSArray *)flags;
@end

 JFFlag.m

#import "JFFlag.h"

@implementation JFFlag

//快速创建模型
- (instancetype)initWithDictionary:(NSDictionary *)dict {
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}
+ (instancetype)flagWithDictyinary:(NSDictionary *)dict {
    return [[self alloc] initWithDictionary:dict];
}

//返回模型数组
+ (NSArray *)flags {
    NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil]];
    
    NSMutableArray *arrayM = [NSMutableArray array];
    for (NSDictionary *dict in array) {
        JFFlag *flag = [JFFlag flagWithDictyinary:dict];
        [arrayM addObject:flag];
    }
    return arrayM;
}
@end

在Main.storyboard中搭建界面,这里我们只需要拖拽一个UIPickerView控件即可。

Snip20150829_25

UIPickerView显示的自定义View,也就是类似于我们之前学过的UITableViewCell。这里我们也单独封装自定义View,并用xib来描述这个View,并进行属性连线。

Snip20150829_28

然后实现封装类中的方法,提供一个快速创建自定义View的类方法和为自定义View的子控件赋值的方法。

JFFlagView.h

#import <UIKit/UIKit.h>
#import "JFFlag.h"

@interface JFFlagView : UIView
@property (strong, nonatomic) JFFlag *flag;

//快速创建自定义View
+ (instancetype)flagView;
@end

 JFFlagView.m

#import "JFFlagView.h"
#import "JFFlag.h"

@interface JFFlagView ()
@property (weak, nonatomic) IBOutlet UIImageView *iconView;
@property (weak, nonatomic) IBOutlet UILabel *nameView;

@end

@implementation JFFlagView

//快速创建自定义View
+ (instancetype)flagView {
    //从xib重创建View
    return [[[NSBundle mainBundle] loadNibNamed:@"JFFlagView" owner:nil options:nil] lastObject];
}

//重写set方法为自定义View中的子控件赋值
- (void)setFlag:(JFFlag *)flag {
    _flag = flag;
    
    self.iconView.image = [UIImage imageNamed:flag.icon];
    self.nameView.text = flag.name;
}
@end

最后我们在控制器中懒加载数据,并实现UIPickerView的数据源和代理方法,进行数据的展示。

ViewController.m

#import "ViewController.h"
#import "JFFlag.h"
#import "JFFlagView.h"

@interface ViewController () <UIPickerViewDataSource,UIPickerViewDelegate>
@property (strong, nonatomic) NSArray *flags;
@property (weak, nonatomic) IBOutlet UIPickerView *pickerView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //指定数据源和代理对象
    self.pickerView.dataSource = self;
    self.pickerView.delegate = self;
}

//懒加载
- (NSArray *)flags {
    if (_flags == nil) {
        _flags = [JFFlag flags];
    }
    return _flags;
}

//一共有多少列数据,注意和UITableView不同的是,就算是只有一列,这个方法也不能省略
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 1;
}

//每组有多少行
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    return self.flags.count;
}

//返回每行的View
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
    
    JFFlag *flag = self.flags[row];
    
    //创建自定义View并赋值数据
    JFFlagView *flagView = [JFFlagView flagView];
    flagView.flag = flag;
    
    return flagView;
}

//返回第componet列的行高
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
    return 50;
}

@end

 注意:在iOS6之后,返回的View就不能重用了,因为苹果觉得UIPickerView展示的数据量都非常小,没有必须重用。如果是在iOS6之前,也可以重用View。

四、UIDatePicker

UIDatePicker是一个日期选择器控件,他的用法和UIPickerView类似。UIDatePicker继承自UIControl,所以可以添加监听事件。但是有些童鞋认为UIDatePicker是UIPickerView的子类,事实不是这样的,只是在UIDatePicker内部封装了一个UIPickerView,所以不要弄错了啊。并且UIDatePicker控件经常和UIToolBar一起使用,也就是给日期选择器添加一个工具条。

//1.常见属性
// datePicker的显示模式
@property (nonatomic) UIDatePickerMode datePickerMode;
// 显示的区域语言
@property (nonatomic, retain) NSLocale   *locale;

//2.监听UIDatePicker的选择
* 因为UIDatePicker继承自UIControl,所以通过addTarget:...监听

//3.代码创建UIDatePicker
// 1.创建日期选择器
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
// 2.设置日期显示区域
datePicker.locale = [NSLocale localeWithLocaleIdentifier:@"zh"];
// 3.设置日期模式
datePicker.datePickerMode = UIDatePickerModeTime;
// 4.0 设置最小时间(从当前时间起的前20年)
datePicker.minimumDate = [NSDate dateWithTimeIntervalSinceNow:-(365 * 24 * 3600 * 20)];
// 4.1 设置最大时间(从当前时间起的后20年)
datePicker.maximumDate = [NSDate dateWithTimeIntervalSinceNow:365 * 24 * 3600 * 20];
// 5.设置时间间隔。(该间隔要能够让60整除)
datePicker.minuteInterval = 6;

 五、UIPickerView基本使用方法

使用UIDatePicker和UIPickerView完成选择生日和省份的功能,自定义键盘其实就是为UITextField的inputView赋值一个自定义View,工具条则是为UITextField的inputAccessoryView赋值一个自定义的View。

自定义UITextField弹出的键盘:

这里第一个文本框的inputView赋值一个UIDatePicker,inputAccessoryView则是一个UIToolBar。

第二个文本框的inputView赋值一个UIPickerView,inputAccessoryView也是一个UIToolBar。

10

创建项目,在storyboard中搭建界面,这里只需要拖拽两个UILabel和UITextField即可,其他控件需要代码创建 。

Snip20150829_5

搭建好界面先来创建模型,导入plist文件观察可轻易发现数据的结构。每一个字典中有两个键值对,一个是城市数组,另一个是省份字符串。由此可以确定,UIPickerView的第一列显示的数据就是所有省份,第二列显示的数据则是每个省份对应的城市,而不是固定的数据。

Snip20150829_1

字典转模型,返回模型数组。已经写了无数遍了,就不再解释了。提供一个返回模型数组的类方法,方便懒加载时直接调用这个类方法返回模型数组。

Province.h

#import <Foundation/Foundation.h>

@interface Province : NSObject
@property(nonatomic,strong) NSArray *cities;
@property(nonatomic,copy) NSString *name;

//快速创建模型对象
+ (instancetype)provinceWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;

//返回模型数组
+ (NSArray *)provinces;
@end

Province.m

#import "Province.h"

@implementation Province
//快速创建模型对象
+ (instancetype)provinceWithDict:(NSDictionary *)dict{
    return [[self alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

//返回模型数组
+ (NSArray *)provinces{
    NSArray *provinceArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cities.plist" ofType:nil]];
    NSMutableArray *provinces = [NSMutableArray array];
    for (NSDictionary *dict in provinceArray) {
        Province *province = [self provinceWithDict:dict];
        [provinces addObject:province];
    }
    return provinces;
}
@end

封装好模型后,在控制器中懒加载模型数组。并完成其他一系列调用,具体代码如下:

ViewController.m

#import "ViewController.h"
#import "Province.h"
@interface ViewController ()<UIPickerViewDataSource,UIPickerViewDelegate,UITextFieldDelegate>

//文本框控件
@property (weak, nonatomic) IBOutlet UITextField *inputField;
@property (weak, nonatomic) IBOutlet UITextField *provinceField;

//日期选择器,注意懒加载的控件需要用strong而不是weak
@property(nonatomic,strong) UIDatePicker *datePicker;

//工具导航控件,注意懒加载的控件需要用strong而不是weak
@property(nonatomic,strong) UIToolbar *toolBar;

//模型数组
@property(nonatomic,strong) NSArray *provinces;

//当前选中的省份
@property(nonatomic,strong) Province *selectedProvince;

@end

@implementation ViewController

//懒加载模型数组
- (NSArray *)provinces {
    if (_provinces == nil) {
        _provinces = [Province provinces];
    }
    return _provinces;
}

//懒加载工具条控件
- (UIToolbar *)toolBar {
    if (_toolBar == nil) {
        
        //创建键盘工具条
        UIToolbar *toolbar = [[UIToolbar alloc] init];
        toolbar.frame = CGRectMake(0, 0, 320, 44);
        toolbar.barTintColor = [UIColor orangeColor];
        toolbar.tintColor = [UIColor whiteColor];
        
        //上一个
        UIBarButtonItem *lastItem = [[UIBarButtonItem alloc] initWithTitle:@"上一个" style: UIBarButtonItemStyleDone target:self action:@selector(lastClick)];
    
        //下一个
        UIBarButtonItem *nextItem = [[UIBarButtonItem alloc] initWithTitle:@"下一个" style: UIBarButtonItemStyleDone target:self action:@selector(nextClick)];
        
        UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
        
        //完成
        UIBarButtonItem *complete = [[UIBarButtonItem alloc] initWithTitle:@"完成" style: UIBarButtonItemStyleDone target:self action:@selector( complete)];
        
        toolbar.items = @[lastItem,nextItem,flexibleSpace,complete];
        
        _toolBar = toolbar;
    }
    return _toolBar;
}

//懒加载时间选择器控件
- (UIDatePicker *)datePicker {
    if (_datePicker == nil) {
        
        //创建日期选择器
        UIDatePicker *datePicker = [[UIDatePicker alloc] init];
        //设置日期显示区域
        datePicker.locale = [NSLocale localeWithLocaleIdentifier:@"zh"];
        //设置日期模式
        datePicker.datePickerMode = UIDatePickerModeDate;
        
        //设置最小时间
        datePicker.minimumDate = [NSDate dateWithTimeIntervalSinceNow:-(365 * 24 * 3600 * 20)];
        //设置最大时间
        datePicker.maximumDate = [NSDate dateWithTimeIntervalSinceNow:365 * 24 * 3600 * 20];
        
        //设置时间间隔,默认为1,设置的值必须能被60整除
        datePicker.minuteInterval = 6;
        
        //监听工具条的点击
        [datePicker addTarget:self action:@selector(datePickerClick:) forControlEvents:UIControlEventValueChanged];
        _datePicker = datePicker;
    }
    return  _datePicker;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //设置文本输入框的代理
    self.inputField.delegate = self;
    self.provinceField.delegate = self;
    
    //默认显示当天日期
    [self datePickerClick:nil];
}

#pragma mark - 数据源方法
//城市选择一共有多少列
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return  2;
}

//城市选择第component列有多少行
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    
    if (component == 0) {
        //第一列,显示所有省份,所以就是模型数组的长度
        return self.provinces.count;
    } else {
        //获取当前第一列中选中的数据的索引
        NSInteger provinceIndex = [pickerView selectedRowInComponent:0];
        //根据这个索引获取当前选中的数据的模型对象
        Province *province = self.provinces[provinceIndex];
        //返回这个模型对象中cities属性的长度,也就是对应城市个数
        return province.cities.count;
    }
}

#pragma mark - 代理方法
//第component列的第row行显示什么数据
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
    if (component == 0) {
        //显示哪个省
        Province *province = self.provinces[row];
        return province.name;
    } else {
        //显示哪个城市
        //获取当前第一列中选中的数据的索引
        NSInteger provinceIndex = [pickerView selectedRowInComponent:0];
        //根据这个索引获取当前选中的数据的模型对象
        Province *province = self.provinces[provinceIndex];
        
        if (row >= province.cities.count) return nil;
        
        return province.cities[row];
    }
}

//选中第 component 列的第row 行
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    if (component == 0) {
        //改变了省份
        self.selectedProvince =  self.provinces[row];
        
        //刷新第1列的数据(重新刷新数据,重新调用数据源和代理的相应方法获得数据)
        [pickerView reloadComponent:1];
        //选中第1列的第0行
        [pickerView selectRow:0 inComponent:1 animated:YES];
    }
    NSString *cityName = self.selectedProvince.cities[component == 0 ? 0:row];
    self.provinceField.text = [NSString stringWithFormat:@"%@  %@",self.selectedProvince.name,cityName];
}


#pragma mark - 监听按钮的点击事件
//完成按钮
- (void)complete{
    //确保用户是选中了省份的
    if ([self.provinceField isFirstResponder] && self.selectedProvince && self.provinceField.text.length == 0) {
        self.provinceField.text = [NSString stringWithFormat:@"%@  %@",self.selectedProvince.name,self.selectedProvince.cities[0]];
    }
    
    [self.view endEditing:YES];
    
}
//上一个按钮
- (void)lastClick{
    if (self.provinceField.isFirstResponder) {
        [self.inputField becomeFirstResponder];
    }
}
//下一个按钮
- (void)nextClick{
    if (self.inputField.isFirstResponder) {
        [self.provinceField becomeFirstResponder];
    }
}

#pragma mark - 监听日期选择器时间的改变
- (void)datePickerClick:(UIDatePicker *)datePicker{
    
    //获取NSDate对象
    NSDate *date = datePicker == nil ? [NSDate date]:[datePicker date];
    
    //创建时间格式化对象
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    
    //指定时间格式
    formatter.dateFormat = @"yyyy-MM-dd";
    
    //将时间赋值给文本框
    self.inputField.text = [formatter stringFromDate:date];
}

#pragma mark - 文本输入框代理
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    
    if(textField.inputView) return YES;
    
    if (textField == self.inputField) {
        
        //自定义生日文本输入框的键盘
        self.inputField.inputView = self.datePicker;
        //给键盘添加配置工具条
        self.inputField.inputAccessoryView = self.toolBar;
        
    } else if(textField == self.provinceField) {
        
        //默认被选中的省份为数组中的一个省份
        self.selectedProvince = self.provinces[0];
        
        //自定义省份文本输入框的键盘
        UIPickerView *pickerView = [[UIPickerView alloc] init];
        
        //指定数据源、代理对象
        pickerView.dataSource = self;
        pickerView.delegate = self;
        
        //自定义省份输入框的键盘
        self.provinceField.inputView = pickerView;
        //给键盘添加配置工具条
        self.provinceField.inputAccessoryView = self.toolBar;
    }
    return YES;
}
@end

六、项目中的常见文件

Info.plist是整个项目的重要配置文件不能删除。

Localization native development region :本地化相关。

Bundle display name :程序安装后显示在iphone/ipad上的名字。

Icon file :程序的图标,Xcode5以前创建的项目有,一般用Icon.png,Xcode5以后创建的不在plist设置,在Images.xcassets设置。

Bundle version :程序版本号,AppStore每更新版本,版本要增加,内部项目管理的版本号,不对外。

Bundle versions string, short :用于itunes上显示的版本号,即对外的版本。一般3个数组成。

Bundle identifier :应用的惟一标识,发布到AppStore去。

pch文件干什么用?

pch文件里的内容被项目中的其它所有资源共享访问,定义宏 身高、电话和其它文件共享使用、自定义日志输出。我们在开发阶段,在pch中使用DEBUG模式自定义日志输出宏,当app发布后这些宏就失效。

#ifdef DEBUG //开发阶段
#define Log(...) NSLog(__VA_ARGS__)
#else //发布阶段
#define Log(...)
#endif

因为pch中的内容会呗项目中其它所有资源共享访问,所以有可能我们项目中不只是有oc文件,还会有其它语言文件。所以我们在定义被共享的内容时,应该加判断。

#ifdef __OBJC__
//这里的内容是只有.m、.mm文件中才能访问
#endif

注意:一般公用的资源写在#ifdef __OBJC__里面。

七、App启动原理

UIApplication

什么是UIApplication?

1.UIApplication是整个应用程序的象征,就像中国的象征是五星红旗。

2.每一个应用都有自己的UIApplication,而且是单例,通过[UIApplication sharedApplication]获取。单例对象也就是程序运行到结束,只能有一个对象。如果我们使用[[UIApplication alloc] init]创建对象,程序不被允许并且会直接报错,因为application只有能一个对象。

3.ios程序启动后创建的第一人对象就是UIApplication对象。

UIApplication用来用来设置全局性的东西

设置网络请求状态/取消网络请求状态

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

设置应用图标数字/清除图标数据

[UIApplication sharedApplication].applicationIconBadgeNumber = 2;

不过在iOS8更新后不能直接这样设置应用图标的数字了,需要先获得用户的授权,才能显示。完成代码如下:

    //创建单例对象
    UIApplication *app = [UIApplication sharedApplication];

    //判断系统版本是否超过8.0
    if ([[[UIDevice currentDevice] systemVersion] doubleValue] >= 8.0) {
        //如果超过就要获取用户的授权
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    }
    //设置数字
    app.applicationIconBadgeNumber = 10;

设置状态栏样式

方式1.由控制器的一个方法决定

- (UIStatusBarStyle)preferredStatusBarStyle;

方式2.使用application设置

[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;

但是这样设置默认是不起作用的,因为默认状态栏样由控制器来管理,如果想用application设置状态栏有效,得在Info.plist的设置View controller-based status bar appearance = NO

打电话、发短信、发邮件、打开网站

调用application的openURL方法即可,这里就只演示打开网站,其他同理。

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"mailto://1012@qq.com"]];

UIWindow

什么是UIWindow?

1.UIWindow是用来显示控制器的View的。

2.每一个应用程序都有一个窗口。

演示UIWindow,在没有设置主要storyboard的情况下

首先在info.plist文件中清空Main storyboard file base name的value。

Snip20150829_6
在AppDelegate.m文件中的didFinishLaunchingWithOptions方法中创建窗口,设置为主窗口并可见。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //创建窗口
    UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    
    //设置颜色
    window.backgroundColor = [UIColor purpleColor];
    
    //窗口为什么要有根控制器,是因为窗口上要显示视图
    UIViewController *vc = [[UIViewController alloc] init];
    vc.view.backgroundColor = [UIColor grayColor];
    
    window.rootViewController = vc;
    //这一步内部其实是添加控制器的view到窗口上
    //[window addSubview:vc.view]
    
    //窗口显示的时候,一定要设置为主窗口并可见
    [window makeKeyAndVisible];
    
    self.window = window;
    
    return YES;
}

效果如下:

Snip20150829_7

窗口是一个特殊的UIView对象,可以往window添加子控件,如label、switch等等。但是一般不会在窗口添加子控件,会设置窗口的rootViewController属性,将控制器的view添加到窗口上。

注意:如果直接把控制器的view添加到窗口是不能让控制的view进行旋转的,但设置窗口的根控制器,控制器的view可以旋转。因为旋转事件传递是由UIApplication -> UIWindow -> 根控制器,窗口不会做旋转处理,只有控制器才会。所以别直接将UIView添加到UIWindow上面

获取主窗口方式

一个窗口当前能接受键盘和非触摸事件时,便被认为是主窗口。还有下面三种方式都能获取主窗口:

[UIApplication sharedApplication].delegate.window
[UIApplication sharedApplication].keyWindow
self.view.window

上面的情况是在info.plist中没有指定主要的storyboard,才需要手动创建UIWindow,并创建控制器赋值给UIWindows的rootViewController属性,再设置UIWindow为主窗口并显示。

还有一种情况就是在已经在info.plist中指定了主要的storyboard,这样程序会自动创建窗口,设置窗口的根控制器为storyboard的控制器,让窗口成为主窗口并显示。

App启动原理

我们从第一天开始学习C语言就知道,程序是入口是main函数,OC也不例外!所以程序运行首先执行main函数,main函数就一行代码,不过这一行代码可做了不少事情,main函数如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

1.程序运行后根据main函数中第三个参数创建UIApplication对象,UIApplication是程序中创建的第一个对象。这个参数如果为nil默认就是UIApplication,如果换成我们自己创建的类,就必须继承自UIApplication。

2.根据main函数中第四个参数创建UIApplication的代理对象,并赋值给UIApplication对象的delegate属性,并开启Main Runloop(事件循环)。

3.进行事件的处理,首先会在程序启动完毕后调用delegate对象的application:didFinishLaunchingWithOptions:方法。此时要分两种情况进行处理了,首先会去info.plist文件中查找是否设置了主要的storyboard文件名。

如果没有设置storyboard:我们需要手动在AppDelegate.m中的application:didFinishLaunchingWithOptions:中创建UIWindow,然后创建和设置UIWindow的rootViewController。最后设置UIWindow为主窗口并可见。

已经设置storyboard:根据Info.plist获得最主要storyboard的文件名,加载最主要的storyboard。自动创建UIWindow,自动创建和设置UIWindow的rootViewController为当前storyboard的控制器(再次提醒:这一步其实是将rootViewController中控制器的view添加到窗口上显示)。最后设置UIWindow为主窗口并可见。