学习目标
1.【掌握】控制器的创建方式
2.【了解】导航控制器的介绍
3.【掌握】代码创建导航控制器
4.【掌握】storyboard中创建导航控制器
5.【理解】控制器的生命周期
一、控制器的创建方式
视图控制器就是用来控制或者说管理界面(视图)的,换句话说界面长得丑与美由视图控制器说的算。每个视图控制器(UIViewController)都有一个View属性来描述界面长什么样,上一篇文章中提到的设置窗口的根控制器其实就是会把视图控制器的里View会添加在窗口上进行显示。
UIStoryboard的方式
获取storyboard箭头所指的视图控制器,以这种方式创建控制器需要确定需要被创建的控制器已经勾选了Is Initial View Controller选项。
在AppDelegate.m中的application:didFinishLaunchingWithOptions:方法中创建控制器。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //创建窗口 UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //获取storyboard对象 UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; //获取箭头所指的控制器 id vc = [mainStoryboard instantiateInitialViewController]; //设置根控制器 window.rootViewController = vc; //设置主窗口并可见 [window makeKeyAndVisible]; self.window = window; return YES; }
获取storyboard标识了ID所指的视图控制器,假如一个storyboard中有多个控制器,并且我们需要创建的控制器并不是箭头所指向的控制器,就可以用这种方式来创建了。
以storyboard标识符创建对应控制器的方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //创建窗口 UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //获取storyboard对象 UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; //通过标识获取storyboard的控制器 id vc = [mainStoryboard instantiateViewControllerWithIdentifier:@"RedVc"]; //设置根控制器 window.rootViewController = vc; //设置主窗口并可见 [window makeKeyAndVisible]; self.window = window; return YES; }
直接创建控制器
不通过storyboard,直接使用代码创建视图控制器的方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //创建窗口 UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //直接创建控制器 UIViewController *vc = [[UIViewController alloc] init]; vc.view.backgroundColor = [UIColor greenColor]; //设置根控制器 window.rootViewController = vc; //设置主窗口并可见 [window makeKeyAndVisible]; self.window = window; return YES; }
创建带xib的视图控制器
xib也是描述界面长什么样的,我们之前使用xib都是用来描述View的。其实也可以使用xib来创建控制器,不过需要注意以下事项:
使用xib创建视图控制器,需要我们手动创建继承自UIViewController的视图控制器类和xib文件。这里我创建了一个视图控制器类和两个xib文件,来演示加载xib的优先级。
特别注意的是:如果我们的xib文件和视图控制器类是单独创建的,需要我们手动指定File's Owner的View。如果在创建视图控制器类的同时就勾选同时创建xib则可省略这一步。如下图所示:
创建视图控制器类的同时勾选Also create XIB file。
指定xib创建视图控制器的方法,会创建我们指定名称的xib的视图为控制器的view。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //创建窗口 UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //使用xib方法创建控制器 OneViewController *vc = [[OneViewController alloc] initWithNibName:@"OneViewController" bundle:nil]; //设置根控制器 window.rootViewController = vc; //设置主窗口并可见 [window makeKeyAndVisible]; self.window = window; return YES; }
不指定xib创建视图控制器的方法,会根据我们的视图控制器的类名去查找,优先查找和类名去掉Controller后相同的xib文件,如果找不到才去找和类名相同的xib文件。不过强烈建议,我们开发中一定要让视图控制器的类名和xib名保持一致,否则被同事打死了不划算了。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //创建窗口 UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //不指定xib创建视图控制器,这里创建的是用OneView.xib创建的视图控制器,因为他的优先级更高 OneViewController *vc = [[OneViewController alloc] init]; //设置根控制器 window.rootViewController = vc; //设置主窗口并可见 [window makeKeyAndVisible]; self.window = window; return YES; }
还有一点特别需要注意的是:如果我们的视图控制器类中实现了loadView方法,创建视图控制器将不会再根据xib中的view来创建,而是根据loadView方法中的代码来创建。如果这个方法的实现为空,则创建一个空白的view。但是如果控制器是从storyboard创建,loadView从storyboard加载view。
//初始化控制器view - (void)loadView{ //这里写初始化view的代码 }
二、导航控制器的介绍
一个app一般都是由多个界面组成的,只用一个界面都能写出的app一般都非常的简单,市面上也没看见几款app只有一个界面。如果要创建多个界面就得创建多个控制器,为了便于管理多个控制器,apple提供了两个特殊的控制器来管理多个子控制器的来回切换,也就是导航控制器(UINavigationController、UITabBarController)。
UINavigationController导航控制器:
UITabBarController导航控制器:
三、代码创建导航控制器
代码创建导航控制器的步骤:
1.在AppDelegate.m的application:didFinishLaunchingWithOptions:的方法中创建导航控制器。
UINavigationController *nav = [[UINavigationController alloc] init];
2.将导航控制器设置给window的rootViewController属性,也就是设置窗口的根控制器。
self.window.rootViewController = nav;
3.创建子控制器,push给导航控制器。
UIViewController *cv = [[UIViewController alloc] init]; cv.view.backgroundColor = [UIColor redColor]; [nav pushViewController:cv animated:NO];
全部代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.backgroundColor = [UIColor whiteColor]; //创建导航控制器 UINavigationController *nav = [[UINavigationController alloc] init]; //创建3个子控制器 UIViewController *cv1 = [[UIViewController alloc] init]; cv1.view.backgroundColor = [UIColor redColor]; UIViewController *cv2 = [[UIViewController alloc] init]; cv2.view.backgroundColor = [UIColor blueColor]; UIViewController *cv3 = [[UIViewController alloc] init]; cv3.view.backgroundColor = [UIColor yellowColor]; //将三个子控制器push给导航控制器,最终显示出来的只有最后一个push的子控制器 [nav pushViewController:cv1 animated:YES]; [nav pushViewController:cv2 animated:YES]; [nav pushViewController:cv3 animated:YES]; //将导航控制器作为窗口的根控制器 self.window.rootViewController = nav; [self.window makeKeyAndVisible]; return YES; }
最终显示的效果:
导航控制器是以栈的方式来管理其内部的子控制器。栈的特点是先进后出或后进先出,说通俗点就是一次性push多个子控制器到导航控制器,是显示最后push的那个子控制器,先push的只是被覆盖了,并没有被销毁,这就是进栈。出栈就是上面点击back按钮后会返回上一个子控制器,出栈的子控制器才会被销毁。
栈顶原理
1.导航控制器将所要显示的子控制器放到一个栈中。
2.导航控制器显示的第一个控制器为栈顶控制器。
3.返回上一个子控制器时,栈顶控制器的view从导航控制器的view中移除,并栈顶控制器将被销毁。
4.把最新的栈顶控制器的view显示在导航控制器view的上面。
5.所有的子控制器会存储在导航控制器的viewControllers属性中。
该方式管理子控制器的缺点:浪费内存,有可能用户只想查看第一个界面,而不关心其他界面。创建其他界面就会消耗性能,浪费内存。
使用导航控制器管理子控制器最理想的做法是
应用一启动只创建导航控制器和第一个要显示的子控制器,其他控制器是否创建取决于用户的相关操作。
先熟悉下几个导航控制器的常用控制器切换的方法:
//push到指定控制器,也就是将指定控制器压入栈顶 - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated; //返回上一个控制器,也就是出栈,当前子控制器会被销毁 - (UIViewController *)popViewControllerAnimated:(BOOL)animated; //返回到指定控制器 - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated; //返回到导航控制器的根控制器,也就是第一个push到根控制器的视图控制器 - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated;
下面来演示一下如何利用导航控制器管理多个控制器:
首先创建一个空项目(没有Main.storyboard),然后创建三个子视图控制器,在创建子视图控制器的同时创建与之对应的xib。
为每一个xib中的View设置背景色,并设置三个Label用于区分控制器。
在AppDelegate.m中创建窗口并为窗口的根控制器设置一个导航控制器,然后为导航控制器push第一个子控制器。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //创建一个导航控制器 UINavigationController *nav = [[UINavigationController alloc] init]; //将导航控制器设置为窗口的根控制器 self.window.rootViewController = nav; //创建子控制器,push到栈中 [nav pushViewController:[[JFOneViewController alloc] init] animated:YES]; [self.window makeKeyAndVisible]; return YES; }
然后分别实现每个导航控制器中的button点击监听方法。
JFOneViewController.m
#import "JFOneViewController.h" #import "JFTwoViewController.h" @implementation JFOneViewController - (IBAction)btnDidClick { //创建要跳转的控制器 JFTwoViewController *two = [[JFTwoViewController alloc] init]; //将要跳转的控制器push到栈顶,要调用这个方法。One控制器必须已经在栈中。 [self.navigationController pushViewController:two animated:YES]; } @end
JFTwoViewController.m
#import "JFTwoViewController.h" #import "JFThreeViewController.h" @implementation JFTwoViewController - (IBAction)btnDidClick { //创建要跳转的控制器 JFThreeViewController *three = [[JFThreeViewController alloc] init]; //将要跳转的控制器push到栈顶,要调用这个方法。One控制器必须已经在栈中。 [self.navigationController pushViewController:three animated:YES]; } @end
JFThreeViewController.m
#import "JFThreeViewController.h" @implementation JFThreeViewController - (IBAction)btnDidClick { //返回第一个子控制器,也就是将除根控制器外的控制器全部出栈 [self.navigationController popToRootViewControllerAnimated:YES]; } @end
最终效果如下所示:
每一个控制器有个navigationItem属性,可以为子控制器设置顶部标题、返回按钮等。使用navigationItem可设置标题,设置标题还可以使用控制器的title属性。navigationItem还有一个titleView属性,是自定义标题的View,也是很常用的。
- (void)viewDidLoad { [super viewDidLoad]; //设置标题 // self.navigationItem.title = @"第一个控制器"; //直接使用控制器的title属性也能设置标题 self.title = @"第一个控制器"; }
效果图:
使用navigationItem还可设置左右两边的按钮,但是如果是设置返回按钮,则当前控制器设置的返回按钮是给下一个控制器显示的。我们在JFOneViewController中设置返回按钮,在JFTwoViewController才能看到效果。
//设置返回按钮 self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStyleDone target:nil action:nil];
可以看到,第二个控制器中的“back”变成了“返回”。效果图:
四、storyboard中创建导航控制器
使用storyboard创建UINavigationController管理多个子控制器,首先我们先将sizeClass关掉,不然屏幕太大了。然后添加导航控制器,这里有多种方式。最简单最暴力的就是将自带的控制器删除,然后拖拽一个UINavigationController进来。这种方式默认会多创建一个UITableViewController并设置为UINavigationController的rootViewController。
或者选中控制器,选择Xcode -> Editor -> Embed In -> Navigation Controller。给当前控制器嵌入一个导航控制器。
注意创建好导航控制器后,应该将导航控制器设为箭头所指向的控制器,这样程序运行就会自带创建箭头所指向控制器的view。这里是导航控制器,所以就会显示导航控制器的根控制器的view。
我这里自己为导航控制器设置rootViewController,选中导航控制器右键,会有一个属性列表。选中root view controller拖到子控制器即可。
这样我们运行程序,看到的就是导航控制器的根控制器的view了。效果图如下所示:
为控制器设置标题,直接双击导航条中间即可设置。
效果图:
再拖拽一个ViewController,设置view背景色为绿色。然后再之前红色view中添加一个按钮,并按住control键将按钮连线到下一个控制器,选择push。这里一共有三个选项,push就是将下一个控制器压入栈顶。modal是模态,也是实现控制器切换,只不过modal是从控制器底部向上滑动直到覆盖住红色view。custom是自定义,这个后面再说。
以相同的方式再拖拽一个控制器并在第二个控制器的view中中添加一个按钮push到第三个控制器。
效果图:
实现第三个控制器切换到第一个控制器是不能再使用push的,因为push的原理是将控制器压入栈顶。
因为第一个控制器是在栈底,我们压入到栈顶的控制器是新创建的一个控制器压入栈顶的,而不是原先栈底的控制器。
我们应该将栈里第一个控制器上顶部的其他控制器全部出栈,这样第一个控制器才成为栈顶控制器。所以我们只能通过代码来实现了,实现方式和第一个案例类似,也是调用下面的方法来实现。
//将根控制器以为的其他控制器出栈 - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated;
要实现这个功能,我们首先得创建第三个控制器的自定义视图控制器类。方法连线并实现。最终效果图:由此可以得出,我们必须熟练掌握代码创建的方法,因为storyboard中能实现的代码都能实现,但代码能实现的storyboard不一定能够实现的。所以,还是好好敲代码吧。
五、控制器的生命周期
楼主怎么不更新了