实际开发中都是多控制器的;用一个控制器(父)管理多个控制器(子)
ios提供2个特殊的(父)控制器
UINavigationControler
- 简介
导航控制器,可以轻松完成多个控制器之间的切换,其结构包含导航条(y=20)、栈顶控制器的view、导航控制器的view。导航控制器需要设置一个根控制器,一般是UIViewControler。 - 基本使用
最开始的时候,栈顶控制器的view就是导航控制器的根控制器的view。
1.先去掉Info.plist的倒数第三行的main,以采用代码方式创建
2.在AppDelegate.m中:
其中,先创建窗口,再创建导航控制器(同时创建了一个UIviewControler的控制器,作为其根控制器),然后把该窗口的根控制器设为所创建的导航控制器,最后显示窗口。
PS:一般地,导航控制器的根控制器来自新建一个UIViewControler类,然后import进来。另外,只要一个控制器是导航控制器的子控制器,那么导航控制器就会成为这个控制器的一个属性(可以拿到 )
- 控制器之间的跳转:
根控制器的导航控制器调用pushViewControler方法,参数为跳转后的控制器。跳转后,显示的是后来这个ViewControler的view,也成了栈顶控制器的view,同时从window上移除了前一个控制器的view,但仍保存于导航控制器的子控制器数组中。
PS:导航控制器是其根控制器的一个属性。
- 导航控制器的子控制器
一般保存在数组(以栈的形式)(先进后出)中
PS:使用setViewControllers方法可以一次压入多个控制器,但是只会显示最后压入的那个控制器的view(栈顶控制器)
[nav setViewControllers:@[vc1,vc2,vc3] animated:YES];
获取栈顶控制器:navController.topViewController
获取当前可见视图的控制器:.visibleViewController
-
控制器的返回
是跳转操作的逆向。执行返回操作后,先从window中移除当前控制器的view,同时把该控制器(在栈顶)从栈中移除。此时在栈顶的控制器的view会添加到window,显示出来,从而实现返回。
注意:
要回到指定的控制器,这个指定的控制器必须得是导航控制器的子控制器。
当一个控制器被pop后,控制器内存就被释放了(会调用deinit/dealloc函数。
控制器被销毁,它的view不一定就销毁(只要有storng指针),但是当一个控制器被销毁,那么它的view的业务逻辑无法处理。
设置导航条的内容
由栈顶控制器的navigationItem属性来决定导航条的内容,在栈顶控制器的.m的viewDidLoad{ }中设置
其中,titleView可以设为各种控件
设置navBar的字体大小,颜色
[nav.navigationBar setTitleTextAttributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:19], NSForegroundColorAttributeName:[UIColor whiteColor] }]; navBar的隐藏状态:navController.navigationBarHidden = YES;
UINavigationBar通过UINavigationItem堆栈,按照如下方式来决定展示在UINavigationBar中的内容
- 位于中间的标题会根据下方顺序选择展示的内容:
如果topItem设置了标题视图(titleView属性), 则展示标题视图
如果topItem设置了标题文字(title属性), 则展示标题文字
如果以上都未设置, 则展示空白。 - 位于右侧的按钮会根据下方顺序选择展示的内容:
如topItem设置了右侧按钮(rightBarButtonItem属性)则展示右侧按钮
如果以上都未设置, 则展示空白。 - 位于左侧的按钮会根据下方顺序选择展示的内容:
如topItem设置了左侧按钮(leftBarButtonItem属性), 则展示左侧按钮
如backItem设置了返回按钮(backBarButtonItem), 则展示返回按钮
如backItem设置了标题(title), 则展示用标题文字封装的返回按钮 - 如果以上都未设置, 则展示利用文字"Back"封装的返回按钮。
设置导航条的外观
UINavigationBar类中提供了大量属性/方法用于设置其外观, 我们可以设置其样式、背景颜色、色彩颜色、文字属性,同样, 我们也可以设置其背景图片、阴影图片。
- 样式:navigationBar.barStyle
该属性可为UIBarStyleDefault(白底黑字)、UIBarStyleBlack(黑底白字) - 背景色:navigationBar.barTintColor
- 前景色:navigationBar.tintColor
- 设置背景图片:[navigationBar setBackgroundImage:。。。];
- 阴影图片:navigationBar.shadowImage = [UIImage imageNamed:@""];
区分关系:
NavigaitonBar是导航栏,位于屏幕上方,管理整个导航控制器的navigationItem,它类似navigationcontroller一样,提供了一个栈来管理UINavigationItem。在编程时,一般只设置每个控制器的navigationItem属性。
一个导航控制器管理多个视图控制器(多个视图控制器共享一个导航控制器),而一个导航控制器只有一个UINavigationBar,被管理的多个视图控制器共享这一个UINavigationBar,只要一个视图控制器改变了UINavigationBar的属性则影响是全局的。
每个视图控制器都会有属于自己的UINavigationItem,系统会以懒加载的方式创建一个UINavigationItem显示在UINavigationBar中,改变UINavigationItem只会在当前控制器起作用,不会影响其它控制器。
通过storybord跳转控制器
设根控制器为导航控制器(删掉默认的控制器,拖入nav,设之为默认即添加箭头,添加导航控制器的根控制器(viewcontroler),方法是采用拖线的方式,点击rootcontroler;再拖入一个viewcontroler,实现控制器之间的跳转,方法是拖线(从一个按钮拖到下一个控制器,点击show选项)
设置导航条的内容(可以把其他控件如item等拖到导航条;可以设导航条标题;
自定义导航控制器
- 设置导航条统一的样式
+(void)initialize{ // 获取APP的导航条标识 // UINavigationBar *bar = [UINavigationBar appearance]; //这种写法有bug,它会代表app中所有的导航条,当app中还有自定义导航条时也会被包含 UINavigationBar *bar = [UINavigationBar appearanceWhenContainedIn:[PDNavigationViewController class],nil]; //(获取指定某几个类的导航条标识) //设置nav bar的背景图片(因无法设置背景颜色) [bar setBackgroundImage:[UIImage imageNamed:@"navBar"] forBarMetrics:UIBarMetricsDefault]; //标题字体大小、颜色 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; dict[NSFontAttributeName] = [UIFont systemFontOfSize:22.0]; dict[NSForegroundColorAttributeName] = [UIColor whiteColor]; [bar setTitleTextAttributes:dict]; }
统一样式只需设一次即可。因此,在initialize方法中设置,此方法当当前类第一次初始化的时候才调用,这样就实现只设置一次。当然在加载时viewDidLoad方法也只调用一次,但是一个缺点就是,当要创建多个导航控制器时,会调用(设置)多次。
- 设置统一的返回按钮
重写push方法来实现
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{ [super pushViewController:viewController animated:animated]; //判断当前不是在根控制器下,此时设置leftBarButtonItem if(self.viewControllers.count > 1) viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"nearby"] style:0 target:self action:@selector(back)]; } -(void)back{ [self popViewControllerAnimated:YES]; //pop返回上层 }
(此时就系统没有了滑动返回功能)
- 恢复滑动返回功能
原理:通过清空代理的方式可以恢复
self.interactivePopGestureRecognizer.delegate = nil;
但是存在bug,因为清空之后会带来后续影响,改善如下:
@interface PDNavigationViewController ()<UINavigationControllerDelegate> @property(nonatomic,strong) id popGesture; //用于暂存 @end -(void)viewDidLoad { [super viewDidLoad]; self.popGesture = self.interactivePopGestureRecognizer.delegate; //暂存代理 self.delegate = self; //将代理设为自身 }
在导航控制器代理方法中清空代理,此方法当控制器显示完毕时调用,因此能够拿到当前的控制器
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{ //根控制器中不需滑动返回,此时还原代理 if(self.viewControllers[0] == viewController){ //当前是根控制器时 self.interactivePopGestureRecognizer.delegate = self.popGesture; //还原代理 }else{ //非根控制器时,清空代理( 恢复滑动返回功能 ) self.interactivePopGestureRecognizer.delegate = nil; } }
- 实现全屏滑动返回( 系统默认从左侧滑动才能返回 )
-(void)viewDidLoad { [super viewDidLoad]; //以下代码实现全屏滑动返回 id target = self.interactivePopGestureRecognizer.delegate; self.interactivePopGestureRecognizer.enabled = NO; UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)]; [self.view addGestureRecognizer:pan]; pan.delegate = self; }
解决全屏滑动的bug
//当开始滑动就会调用 -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ //如果是在非根控制器,才允许滑动 BOOL open = self.viewControllers.count > 1; return open; }
作者:Arthur凌