做为MVC设计模式中的C,Controller一直扮演着项目开发中最重要的角色,它是视图和数据的桥梁,经过它的管理,将数据有条有理的展现在咱们的View层上。iOS中的UIViewController是UIKit框架中最基本的一个类。从第一个UI视图到复杂完整项目,都离不开UIViewController做为基础。基于UIViewController的封装和扩展,也可以出色的完成各类复杂界面逻辑。这篇博客,旨在讨论UIViewController的生命周期和属性方法,在最基础的东西上,每每会获得意想不到的惊喜。设计模式
要了解UIViewController,先要弄清楚其生命周期。在面向对象的语言中,是对象,就必定要有生命周期,UIViewController也不例外,生命周期管理Controller的做用范围和时间,也管理其内对象的做用范围和时间。首先,UIViewController中与其生命周期有关的几个函数以下:数组
//类的初始化方法 + (void)initialize; //对象初始化方法 - (instancetype)init; //从归档初始化 - (instancetype)initWithCoder:(NSCoder *)coder; //加载视图 -(void)loadView; //将要加载视图 - (void)viewDidLoad; //将要布局子视图 -(void)viewWillLayoutSubviews; //已经布局子视图 -(void)viewDidLayoutSubviews; //内存警告 - (void)didReceiveMemoryWarning; //已经展现 -(void)viewDidAppear:(BOOL)animated; //将要展现 -(void)viewWillAppear:(BOOL)animated; //将要消失 -(void)viewWillDisappear:(BOOL)animated; //已经消失 -(void)viewDidDisappear:(BOOL)animated; //被释放 -(void)dealloc;
上面这么多的函数,乍一看什么复杂,其实关系什么明朗,除了initialize,init和initWithCoder不是存在全部对象的声明周期中,其余函数都会在UIViewController的声明周期中有序的被调用。那么具体的调用顺序是怎样的呢,最好的办法是实践一下,经过编号打印,结果以下:app
这是一个ViewController完整的声明周期,其实里面还有好多地方须要咱们注意一下:框架
1:initialize函数并不会每次建立对象都调用,只有在这个类第一次建立对象时才会调用,作一些类的准备工做,再次建立这个类的对象,initalize方法将不会被调用,对于这个类的子类,若是实现了initialize方法,在这个子类第一次建立对象时会调用本身的initalize方法,以后不会调用,若是没有实现,那么它的父类将替它再次调用一下本身的initialize方法,之后建立也都不会再调用。所以,若是咱们有一些和这个相关的全局变量,能够在这里进行初始化。dom
2:init方法和initCoder方法类似,只是被调用的环境不同,若是用代码进行初始化,会调用init,从nib文件或者归档进行初始化,会调用initCoder。iphone
3:loadView方法是开始加载视图的起始方法,除非手动调用,不然在ViewController的生命周期中没特殊状况只会被调用一次。ide
4:viewDidLoad方法是咱们最经常使用的方法的,类中成员对象和变量的初始化咱们都会放在这个方法中,在类建立后,不管视图的展示或消失,这个方法也是只会在将要布局时调用一次。函数
5:viewWillAppear:视图将要展示时会调用。布局
6:viewWillLayoutSubviews:在viewWillAppear后调用,将要对子视图进行布局。测试
7:viewDidLayoutSubviews:已经布局完成子视图。
8:viewDidAppare:视图完成显示时调用。
9:viewWillDisappear:视图将要消失时调用。
10:viewDidDisappear:视图已经消失时调用。
11:dealloc:controller被释放时调用。
注意:通过测试,从nib文件加载的controller,只要不释放,在每次viewWillAppare时都会调用layoutSubviews方法,有时甚至会在viewDidAppare后在调用一次layoutSubviews,而重点是从代码加载的则只会在开始调用一次,以后都不会,因此注意,在layoutSubviews中写相关的布局代码十分危险。
咱们知道,当咱们从StoryBoard中加载ViewController时,咱们在Controller中拖拽的视图是能够被初始化的,这里面有一点须要咱们注意,若是咱们须要向controller中视图进行传值设置,经过如下方法获得的Controller中,视图尚未被初始化建立出来:
ViewController2 * viewController2 = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"ViewController2"];
咱们能够在ViewController2的storyBoard中拉一个label,而后关联到头文件中,以下打印,会发现咱们获得controller时,里面的视图对象并无进行建立:
ViewController2 * viewController2 = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"ViewController2"]; NSLog(@"%@",viewController2.label); [self presentViewController:viewController2 animated:YES completion:nil];
打印以下:
能够想象,若是咱们这时候须要对label进行一些属性设置,必然失败。有人提出能够在建立后,手动调如下loadView方法,咱们试一下,结果以下:
能够看到,手动调用loadView后,label是被建立了出来,可是暴漏了一个更严重的问题,系统不在调用ViewDidLoad方法,这是十分有风险的,由于咱们大部分的初始化代码都会放在这个方法里,因此手动调用loadView是一种错误的方法,apple文档声明对于loadView方法,咱们历来都不要手动直接调用,那么咱们如何实现建立后对成员对象进行传值设置呢,iOS9中增长了这样一个方法:
- (void)loadViewIfNeeded NS_AVAILABLE_IOS(9_0);
这个方法十分有用,调用这个方法,会将视图建立出来,而且不会忽略viewDidLoad的调用。
在iOS9中,UIViewController还增长了下面一个布尔值的属性,能够同来判断controller的view是否已经加载完成:
@property(nullable, nonatomic, readonly, strong) UIView *viewIfLoaded NS_AVAILABLE_IOS(9_0);
对于ViewConroller,咱们通常有两种方式建立,一种是用纯代码的方式,一种是与StoryBoard关联,在UIViewController中,有许多方法方便咱们与StoryBoard进行交互联系。
在StoryBoard中进行界面跳转是十分方便的,咱们在StoryBoard中拉入两个ViewController,在一个上面添加一个按钮,点住按钮按住control,将鼠标拉到第二个controller上,会出现以下的跳转选项:
咱们选择一个后,就会在两个controller之间创建一个跳转链接。当咱们运行点击按钮后,会自动从第一个controller跳转到第二个controller。在UIViewController中有以下方法能够对是否跳转进行控制:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(nullable id)sender NS_AVAILABLE_IOS(6_0);
这个方法若是返回NO,自动跳转将不能进行,会被拒绝,须要注意的是,这个方法只会在自动的跳转时被调用,咱们手动使用代码跳转StoryBoard中的链接关系时是不会被调用的,咱们后面讨论。
在执行过上述方法后,若是返回YES,系统还会在执行以下一个方法,做为跳转前的准备,咱们能够在这个方法中进行一些传值操做,这个方法不管使咱们手动进行跳转仍是storyboard中自动跳转,都会被执行:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(nullable id)sender NS_AVAILABLE_IOS(5_0);
sugur对象中封装了相关的ViewController,可使用segue.destinationViewController获取。
segue在StoryBoard中除了用来自动正向跳转外,咱们还能够进行反向的跳转,相似pop和dismiss方法,这种segue被称为unwind sugue。例如,咱们有一个controller1和一个controllert2,要使用unwind segue从2返回1,咱们须要在2中实现以下格式的方法:
- (IBAction)unwindSegueToViewController:(UIStoryboardSegue *)segue { NSLog(@"unwindSegueToViewController"); }
这个方法中的返回值必须为IBAction,参数必须是UIStoryboardSegue,方法名咱们能够本身定义,以后在StoryBoard中的ViewController1中的Exit选项中,咱们会发现多了一个这样的方法:
咱们能够把它链接到viewController2中的一个按钮上:
这样,当咱们点击viewController2中的按钮时,就会返回到咱们第一个ViewController1中了。
固然,在使用unwind segue方法时,也是会有一些回调帮助咱们进行跳转前的设置和传值,UIViewController以下方法会在跳转前调用,返回NO,则不能进行跳转:
-(BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender{ NSLog(@"canPerformUnwindSegueAction"); return YES; }
以后会执行咱们自定义的unwindSegue方法,这个方法中咱们能够什么都不写,模式是会进行跳转的。
咱们除了在Storyboard中拉拉扯扯能够进行控制器的跳转外,咱们也可使用代码来跳转Storyboard中segue链接关系。
在Storyboard中两个控制器间创建一个segue联系,咱们能够取一个名字:
在触发跳转的方法中,使用以下方法进行跳转,这里面的参数id就是咱们取得segue的id:
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(nullable id)sender NS_AVAILABLE_IOS(5_0);
下面三个属性咱们能够获取controller的nib文件名,其storyBoard和其Bundle:
@property(nullable, nonatomic, readonly, copy) NSString *nibName; @property(nullable, nonatomic, readonly, strong) NSBundle *nibBundle; @property(nullable, nonatomic, readonly, strong) UIStoryboard *storyboard NS_AVAILABLE_IOS(5_0);
这部分的内容和方法可能咱们接触用到的并很少,可是在某些状况下,使用这些方法能够大大的方便某些逻辑。
UIViewController里面封装了一个数组,能够存放其子ViewController,系统中使用的例子就是导航和tabBar这类的控制器,咱们使用以下方法能够直接访问这些父的controller:
@property(nullable,nonatomic,weak,readonly) UIViewController *parentViewController;
在咱们进行控制器的跳转时,只要控制器没有被释放,咱们均可以顺藤摸瓜的找到它,使用以下两个方法:
//其所present的contller,好比,A和B两个controller,A跳转到B,那么A的presentedViewController就是B @property(nullable, nonatomic,readonly) UIViewController *presentedViewController NS_AVAILABLE_IOS(5_0); //和上面的方法恰好相反,好比,A和B两个controller,A跳转到B,那么B的presentingViewController就是A @property(nullable, nonatomic,readonly) UIViewController *presentingViewController NS_AVAILABLE_IOS(5_0);
了解了上面方法咱们能够知道,对于反向传值这样的问题,咱们根本不须要代理,block,通知等这样的复杂手段,只须要获取跳转到它的Controller,直接设置便可。举个例子,咱们须要在第二个界面消失后,改变第一个界面的颜色,在第二个controller中只须要下面的代码便可实现 :
self.presentingViewController.view.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1]; [self dismissViewControllerAnimated:YES completion:nil];
单纯的UIViewController中,咱们使用最多的是以下的两个方法,一个向前跳转,一个向后返回:
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion NS_AVAILABLE_IOS(5_0); - (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion NS_AVAILABLE_IOS(5_0);
从方法中,咱们能够看到,有animated这个参数,来选择是否有动画特效,默认的动画特效是像抽屉同样从手机屏幕的下方向上弹起,固然,这个效果咱们能够进行设置,UIViewController有以下一个属性来设置动画特效:
@property(nonatomic,assign) UIModalTransitionStyle modalTransitionStyle NS_AVAILABLE_IOS(3_0);
注意,这个要设置的是将要跳转到的controller,枚举以下:
typedef NS_ENUM(NSInteger, UIModalTransitionStyle) { UIModalTransitionStyleCoverVertical = 0,//默认的,从下向上覆盖 UIModalTransitionStyleFlipHorizontal ,//水平翻转 UIModalTransitionStyleCrossDissolve,//溶解 UIModalTransitionStylePartialCurl ,从下向上翻页 };
除了跳转的效果,还有一个属性能够设置弹出的controler的填充效果,可是这个属性只在pad上有效,在iphone上无效,都是填充到整个屏幕:
@property(nonatomic,assign) UIModalPresentationStyle modalPresentationStyle NS_AVAILABLE_IOS(3_2); //枚举以下 typedef NS_ENUM(NSInteger, UIModalPresentationStyle) { UIModalPresentationFullScreen = 0,//填充整个屏幕 UIModalPresentationPageSheet,//留下状态栏 UIModalPresentationFormSheet,//四周留下变暗的空白 UIModalPresentationCurrentContext ,//和跳转到它的控制器保持一致 UIModalPresentationCustom NS_ENUM_AVAILABLE_IOS(7_0),//自定义 UIModalPresentationOverFullScreen NS_ENUM_AVAILABLE_IOS(8_0), UIModalPresentationOverCurrentContext NS_ENUM_AVAILABLE_IOS(8_0), UIModalPresentationPopover NS_ENUM_AVAILABLE_IOS(8_0) __TVOS_PROHIBITED, UIModalPresentationNone NS_ENUM_AVAILABLE_IOS(7_0) = -1, };
专一技术,热爱生活,交流技术,也作朋友。
——珲少 QQ群:203317592