一. 什么是父子控制器?
一个控制器通过addChildViewController:方法添加多个控制器,被添加的控制器称为子控制器,添加多个子控制器的控制器称为父控制器。
二. 父子控制器关系有什么作用?
(1). 父控制器处理的事件会自动传给子控制器
(2). 子控制器处理的事件会自动传给父控制器
(3). 子控制器可以通过属性parentViewController获取父控制器
(4). 父控制器可以通过属性childViewControllers获取所有子控制器
(5). A控制器添加到B控制器上后,A控制器就会被强引用,即使A控制器的view不正在显示,A控制器和A控制器的view都不会被销毁
三. 父子控制器的使用场景?
当Apple提供的框架不能满足开发者的时候,考虑重新搭建框架。比如UITabBarController的tabBar默认是在底部,想要把它放到顶部或者左边,实现起来会很困难,这时考虑到模仿UITabBarController的功能,搭建新的框架可能会更简单。
四. 父子控制器的使用原则
创建父子控制器关系的方法
一个控制器使用addChildViewController:方法添加子控制器,就创建了父子控制器关系。如果两个控制器的view是父子关系(不管是直接还是间接的父子关系),那么这两个控制器也应该为父子关系,即也应该代码创建父子关系
伪代码解释:
[a.view addSubview:b.view];
[a addChildViewController:b];
// 或者
[a.view addSubview:otherView];
[otherView addSubview:b.view];
[a addChildViewController:b];
五. 搭建框架一般遇到的问题
当点击按钮切换到A界面,如果A界面之前打开过,再次切换到A界面,会重复创建A界面的控制器
原因:每次点击A界面都创建新的控制器,再显示新的A界面。
解决方案:通过懒加载解决控制器的重复问题,但是如果有上百个控制器就需要创建对应的上百个关于控制器的属性,这样不太好。当点击到A界面,切换到A界面后,A界面的控制器马上被销毁,但A界面的view还存在
原因: A界面的控制器是弱引用,刚创建完就被销毁了。
解决方案:与问题(1)一样的解决,但创建属性时要用Strong修饰,强引用控制器。问题(1)的解决方案里,需要创建多个对应控制器的属性
解决方案:创建一个强引用的数组属性,用于存储每一个控制器,存入数组的控制器都会自动被强引用。控制器之间的无法自动传递事件,比如当旋转屏幕时,父子控制器都会自动调用willRotateToInterfaceOrientation:方法
解决方案:不使用问题(3)的解决方案,因为每一个控制器都有childViewControllers属性,它也是一个数组,可以把控制器都添加到父控制器的childViewControllers里,这样既可以解决问题(3),也可以解决问题(4)。当多次切换界面时,控制器的view同时存在(界面是叠加存在的,只看见最上面的界面)
解决方案:在父控制器上添加一个属性,用于保存正在显示的控制器,当每次显示之前先移除前一个控制器的view,在赋值新的控制器,在把要显示的控制器的view添加到显示界面上。
提示:一个对象有没有被销毁,不是看它有没有被父控件或父控制器移除,而是看它有没有被强引用,不被强引用的对象系统会自动销毁。同一个控件,添加到父控件,添加一百次,也相当于添加了一次。addSubView同一个控件,相当于把它挪到最上面。改变一个控件的父控件,直接将它加到其他的控件上,会自动从原来的父控件移除
六. 模仿UITabBarController
注意:要正确运行代码,需要子控制器的索引和按钮的索引一一对应。
#import "ViewController.h"
#import "SJMOneTableViewController.h"
#import "SJMTwoViewController.h"
#import "SJMTreeViewController.h"
@interface ViewController ()
/** 存放正在显示的控制器 */
@property (nonatomic, strong) UIViewController *showingVc;
@end
@implementation ViewController
- (void)viewDidLoad {
// 添加子控制器
[self addChildViewController:[[SJMOneTableViewController alloc] init]];
[self addChildViewController:[[SJMTwoViewController alloc] init]];
[self addChildViewController:[[SJMTreeViewController alloc] init]];
// 默认加载第一个界面
[self btnClick:nil];
}
// 每一个按钮都调用这个事件
- (IBAction)btnClick:(UIButton *)sender {
// 移除前一个控制器的view
[_showingVc.view removeFromSuperview];
NSInteger index = 0;
if (sender == nil) {
index = 0;
} else {
// 获取当前按钮的索引
index = [sender.superview.subviews indexOfObject:sender];
}
// 赋值新的控制器
_showingVc = self.childViewControllers[index];
_showingVc.view.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
[self.view addSubview:_showingVc.view];
}
@end
界面:
七. 补充
添加或移除子控制器都会自动调用控制器的- (void)didMoveToParentViewController:(UIViewController *)parent方法,当添加时,参数为父控制器,当移除时,参数是nil