- 父类是UIResponder(父类是NSObject)
- 概念:凡是继承自UIViewController的对象,都叫做控制器
- 作用:负责处理软件界面的各种事件、负责软件界面的创建和销毁
- 注意:每个UIViewController专门管理一个软件界面(view),它管理的其他所有控件都是这个view的子控件或“孙控件”
// UIViewController用view这个强指针指着它负责的软件界面,所以只要控制器不销毁,软件界面就不会销毁 NSLog(@"%@",viewController.view);
控制器的创建
通过storyboard创建控制器
- 详见storyboard中“系统加载指定storyboard的流程”
通过xib创建控制器
- 步骤:
1. 首先创建一个Xib文件
2. Xib文件需要拖一个View描述控制器的View
3. 需要把Xib上的View与控制器连线,并设置Xib的File'owner为控制器// 通过xib创建窗口的根控制器:initWithNibName // NibName:xib名称 ViewController *vc = [[ViewController alloc] initWithNibName:@"VC" bundle:nil]; self.window.rootViewController = vc;
- xib注意点:
1> xib里面必须有一个view描述控制器的view,因为控制器的view 属性必须有值。
2> xib需要指定描述哪一个控制器,描述UIView不需要,因为xib里面可以描述很多UIView,不能固定死,但是控制器就不一样了,一个xib就用来描述一个控制器。(设置file owner为控制器,即设置custom class)
3> xib里面可能有很多view,需要拖线指明哪个是控制器的view
- 假设通过Xib创建XXXViewController控制器对象顺序
1. 如果nibName不为空,会去创建对应的xib
2. 如果nibName为nil,首先会去查找XXXViewController.xib,如果有,就会去加载XXXViewController.xib
3. 如果没有找到,就会去找XXXView.xib,如果有,也会去加载
4. 都没有找到,就会生成一个空的view
5. init底层就会调用initWithNibName:bundle:(注意不会通过storyboard的方式加载)
6. Xcode7之前是先找XXXView后找XXXViewController
xib如何快速创建控制器的view:
1> 定义新的控制器的时候,勾选xib,会自动搞一个xib描述控制器的view.
2> 会自动生成一个和控制器同名的xib,并且里面设置好了。xib最后会转成Nib文件,关于nib的介绍详见UINib
xib和storyboard的区别 storyboard已经指定了控制器的view,不需要我们管,xib需要我们手动设置view的custom class和owner。
控制器的view
-
控制器View的决定权:[viewController setView:view]——>viwDidLoad——>重写LoadView>storyboard>xib
- 控制器的view是懒加载的
-(UIView *)view { if (_view == nil) { [self loadView]; [self viewDidLoad]; } return _view; }
- loadView的时机与重写loadView的意义
1. 第一次加载控制器的view时才会调用loadView
2. 使用场景:若想自定义控制器的View就可重写这个方法,例如:控制器的View想展示一张图片,或者创建UIWebView等;
3. 注意:一旦重写了loadView,就不要调用[super loadView],否则会重复创建- (void)loadView { // 如果当前控制器是窗口的根控制器,它的view可以不设置尺寸。 UIImageView *vcView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"1"]]; self.view = vcView; }
可以用isViewLoaded方法判断一个UIViewController的view是否已经被加载
只要创建了控制器,无论是通过storyboard,还是xib,还是手动,都会自动创建view,如果没有指定view,创建的是一个空的view
- 控制器view的透明度
//alloc init创建空View,默认几乎透明(几乎透明:alpha > 0.01) ViewController *vc = [[ViewController alloc] init]; vc.view.backgroundColor = [UIColor clearColor];//空view默认的颜色 //vc.view.backgroundColor = [UIColor whiteColor]; // 如果一个View完全透明,可以直接点击后面的东西,可以完全穿透。(完全透明:alpha <= 0.01);另外要注意颜色与透明度无关 vc.view.alpha = 0.02;
控制器的生命周期方法
-
生命周期方法:即控制器的view什么时候创建,什么时候销毁,只要以view开头一般都是控制器view的生命周期
- 具体的生命周期方法:(布局控件方法调用频繁,view显示与消失前都会调用)
// 控制器的view加载完成时调用
- (void)viewDidLoad {
[super viewDidLoad];
//以后打印控制器的View真实尺寸,一般不再viewDidLoad去打印,因为不准确(还在加载数据),在viewDidAppear中打印
}
// 控制器的view即将显示时调用
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
// 控制器的view完全显示时调用
// 一般用来调试控制器view的真实尺寸
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
// 控制器的view即将消失时调用
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
// 控制器的view完全消失的时候调用
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
// 控制器的view即将布局子控件的时候调用
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
}
// 控制器的view布局子控件的完成时候调用
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
}
// ARC中必须要了解
// viewDidLoad -> viewWillAppear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisappear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidDisappear //可以看出布局控件方法调用频繁
// 非ARC
// Unload:卸载,当前view销毁
//- (void)viewWillUnload
//{
//}
// 当前view卸载完成,完全销毁的时候调用
- (void)viewDidUnload
{
self.datas = nil;
}
// MRC中内存机制解释
//- (void)setDatas:(NSArray *)datas
//{
// if (datas != _datas) {
// [_datas release];
// _datas = [datas retain];
// }
//}
当出现内存警告时,控制器的处理流程
- didReceiveMemoryWarning,当控制器接收内存警告的时候调用
-
内存警告传递过程:手机内存不足产生事件->通知应用程序->调用应用程序代理方法->把事件传递给窗口->窗口传给控制器->调用控制器内存警告的方法。
-
当控制器接收内容警告,会销毁没有显示的控制器的view。
调用viewWillUnload,viewDidUnload,销毁控制器的view
viewDidUnload里面一般清空显示在view里面的数据
1. 为什么要清空显示view的数据:展示数据的view都不存在了,这些数据也就没有用处了,因为数据主要是用来展示在view上
2. 清空数据建议使用nil,清空数据,在非arc和arc都通用。arc是不能使用release,而且非arc下,self.datas = nil;做的事情更多。-
didReceiveMemoryWarning会导致viewDidLoad重新调用。
- 当收到内存警告,导航控制器的子控制器的view有可能被干掉,他如果没有显示的话,当下次使用这个控制器的时候就会调用,加载view。
UIViewController管理状态栏
从iOS7开始,系统提供了2种管理状态栏的方式
-
在iOS7中,默认情况下,状态栏都是由UIViewController管理的(每一个UIViewController都可以拥有自己不同的状态栏),UIViewController实现下列方法就可以轻松管理状态栏的可见性和样式
// 设置状态栏是否隐藏 viewController.prefersStatusBarHidden = YES; // 修状态栏样式 - (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; } @end
-
通过UIApplication管理(一个应用程序的状态栏都由它统一管理)
1. 需在info.plist中配置
2. 在Info.plist中做了图中的配置,可能会出现以下警告信息
```objc
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
// 状态栏的可见性
// 隐藏状态栏
app.statusBarHidden = YES;
// 动画隐藏状态栏
[app setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]
```
父子控制器
- 开发原则:如果子控件添加到父控件中时,如果子控件是控制器的view,需要将子控件的控制器添加到父控件的控制器。
- 屏幕控件切换显示时,某个控件不显示,一般是将控件从父控件移除,但不会立即移除对应的子控制器。
一来方便下次使用时快速处理时间与显示视图;
二来是子控制器销毁时,其视图也会被销毁,而不会造成内存泄露。 - 开发中遇到内存警告时,没有显示的控件,要销毁其控制器以释放内存。
- 任何控制器都可以添加子控制器,都可以管理子控制器
- 父控制器的UINavigationController(导航控制器)和UITabBarController(标签控制器),它的子控制器都能拿到,它的底层是:
1. 判断下自己是不是导航控制器和标签控制器的子控制器,ChildViewController不是导航控制器和标签控制器子控制器则继续判断
2. 判断下父控制器是不是导航控制器和标签控制器的子控制器,是就可以拿到
3. 继续一直判断,直到没有父控制器 -
Modal也是同样的原理:
1. 判断下自己是否被Modal,如果被modal,就把自己dismiss
2. 如果不是,判断下自己父控制器是否被modal,如果被modal,就把自己dismiss
3. 一直判断,直到没有父控制器
// 子控制器(只读)
@property(nonatomic,readonly) NSArray<__kindof UIViewController *> *childViewControllers NS_AVAILABLE_IOS(5_0);
// 添加子控制器
- (void)addChildViewController:(UIViewController *)childController NS_AVAILABLE_IOS(5_0);
// 移除出父控制器
- (void) removeFromParentViewController
UIViewController常用属性
// 标题
@property(nullable, nonatomic,copy) NSString *title;
// 父控制器
@property(nullable,nonatomic,weak,readonly) UIViewController *parentViewController;
// modal它的控制器
@property(nullable, nonatomic,readonly) UIViewController *presentedViewController NS_AVAILABLE_IOS(5_0);
// 它modal的控制器
@property(nullable, nonatomic,readonly) UIViewController *presentingViewController NS_AVAILABLE_IOS(5_0);
判断当前ViewController是push还是present的方式显示的
NSArray *viewcontrollers=self.navigationController.viewControllers;
if (viewcontrollers.count > 1)
{
if ([viewcontrollers objectAtIndex:viewcontrollers.count - 1] == self)
{
//push方式
[self.navigationController popViewControllerAnimated:YES];
}
}
else
{
//present方式
[self dismissViewControllerAnimated:YES completion:nil];
}