背景
在IOS开发中,容器类视图控制器有UINavigationController、UITabBarController以及UISplitViewcontroller这么三种。当我们自己要去实现一个视图控制器容器的时候,我们需要做以下操作:
1、我们需要创建一个subViewController
2、调用[self addChildViewController:self.controller1];
3、执行[self didMoveToParentViewController:self.controller1];通知加载结束
4、将subViewController的view添加到容器控制器上,并且调整其大小到一个合适的大小
以上4个步骤就是自定义容器控制器,并且添加一个视图控制器的步骤。但可以思考下,为什么要这么做呢?是不是所有的容器控制器内部都是这么做的呢?UINavigationController是我们平时用的比较多的容器类,我记得我们平时在使用UINavigationController的时候就是调用了pushViewController然后就展示了页面,调用popViewController就销毁了页面,并没有使用过上面的代码,是不是UINavigationController里面封装了这些东西呢?与此同时,我们在使用viewController的时候,并没有像上面那样设置其view的大小,为什么它的大小就是屏幕大小呢?
带着这些疑问,开始研究,容器视图控制器的实现。
从window说起
在我们平时写项目的时候我们可能不会注意到viewController的里这个自带View的大小,因为我们会直接new,然后push,然后我们会理所当然的认为它的大小和屏幕的大小是一样大的,但真的是这样吗?你可能会说,是啊,我都测试过了!那不同的机型苹果里面是如何确定这个视图大小的呢?我们也没在外部配置过,这个又该怎么解释呢?
这个view的大小,或者我们容器中push的viewController的view的大小肯定是被设置过的,但是怎么设置的呢?带着这个疑问,我开始翻看源代码,在UINavigationController中,我找到了loadView,代码里有创建view大小的代码,如下:
再查看loadView是从哪里被调用的,发现是在ViewController这个基类的view的setter方法中被调用的,代码如下:
可以从上面的代码看到,如果已经创建过view,那直接返回这个view的内部实例对象,否则的话会调用loadView,然后调用我们熟悉的viewDidLoad方法,也就是说,当我们使用这个view的时候,那么一定会先创建一个view,保证它是存在的。
看下源代码的注释:
可以看到,和上面的解释是一模一样的。但上面代码创建出来的View大小是320x480的,大小也不是我手机屏幕的大小呀?那这个时候,第一次使用这个view的地方就成了突破口。
我们知道,APP启动以后,我们会设置window的rootViewController,会不会是那里呢?继续找UIWindow的原代码:
我们发现,在其rootViewController的setter方法中,有设置这个rootViewController的大小,但这个frame的大小是self.bounds,那么这个时候我们就应该想到了,为什么我们在写项目的时候,会先初始化这个window,而且是这么写的:
在初始的时候,window的大小就已经被指定为screen的bounds的大小了,然后根控制器的大小也就顺理成章的设置成了窗口这么大。如果我们在这里将window的大小设置为其他大小呢?经过测试果然是,整个app的view大小都会随着变化。
OK,到这里我们就明白了,原来我们在写VC的时候,为什么不需要设置他的视图大小是因为:
1、在设置Window的时候,其设置了根视图控制器的大小
2、当根视图为UINavigationController的时候,每当我们push或者pop的时候,其都会将这些子ViewController的大小设置的和UINavigationController的View一样大小。
上面的问题到现在就可以解答了,UINavigationController内部确实是用了addChildViewController这个方法,并且在内部其设置了子VC的frame大小,所以在当我们push或者pop的时候,不需要管其他的操作。
addChildViewController是个什么玩意?猜测应该是内部维护了一个数组,数组里存放ViewController,是不是这样呢?
果然没错,其内部维护了一个可变的数组,而且里面还调用了willMoveToParentViewController,怪不得网上说加视图控制器是不需要加这个,因为里面已经加过了,这些应该是明白了吧。
一张图说明问题:
突然产生的疑问
既然容器视图控制器内部实际上是维护了一个数组,我们知道,ViewController继承于NSObject,最终展示的是他的view,那它的作用是什么?没有它行不行呢?
我是这么理解的,我觉得他就是一个视图管理器,管理着这个view,真正展示和这个ViewController是没有半点关系的,我们哪怕就是一个很小的视图,我也可以新建一个viewController来管理,到时候只需要将其view加到parentView上展示就可以了。这也就能理解,为什么viewController中有这么几个管理视图的方法:
- viewDidLoad
- viewWillAppear
- viewDidAppear
- ...
这几个方法就是通知我们,此时viewController维护的这个view的状态。
之前就说viewController就是严格验证MVC设计模式来做的,viewController就是担任这个C,视图View就是这个V,因为我们的button一般响应方法都是放在viewController中来写的,而且几乎所有的业务都是放在ViewController中的,view只负责展示而已。
明白了以上这些,自己再去写一个容器视图控制器应该就很简单了吧。无非就是这么3步:
1、维护一个数组,用来保存要管理的子viewController
2、要展示的时候,将一个合适的子viewController的view放add到container上
3、不展示的时候将其移除,并且删除子viewController
留下一个思考题
在使用TabBarViewController的时候,我发现viewDidLoad只被调用了一次,以后就不会被调用了,这个是怎么做的呢?
感兴趣的同学可以留言哈~