以下用
VC
代替ViewController
一、添加childViewController
1.1 步骤:
-
父VC
调用addChildViewController:
方法,建立子VC
和父VC
的联系 - 将
子VC
的根视图添加到父VC
的视图层级上(记得在这个过程设置子VC
根视图的大小和位置) - 添加好约束管理
子VC
根视图的大小和位置 -
子VC
调用didMoveToParentViewController:
1.2 示例:
- (void) displayContentController: (UIViewController*) content {
// 步骤a
[self addChildViewController:content];
// 步骤b
content.view.frame = [self frameForContentController];
// 步骤c
[self.view addSubview:self.currentClientView];
// 步骤d
[content didMoveToParentViewController:self];
}
1.3 注意事项
- 您只需要调用
didMoveToParentViewController:
即可,因为调用addChildViewController:
会主动触发子VC
的willMoveToParentViewController:
被调用,且willMoveToParentViewController:
方法的调用不能放在子VC
根视图已经添加到父VC
的视图层级上之后 - 如果使用AutoLayout,添加约束应该在步骤c之后,且约束只能直接影响
子VC
根视图的大小和位子,而不能添加和直接影响到子VC
视图层级上的子视图
二、移除childViewController
2.1 步骤:
- 调用
子VC
的willMoveToParentViewController:
方法,参数传nil
- 移除添加在
子VC
的根视图上的约束 - 将
子VC
的根视图从父VC
的视图层级上移除 - 调用
子VC
的removeFromParentViewController
方法,终结与父VC
的联系
2.2 示例:
- (void) hideContentController: (UIViewController*) content {
// 步骤a
[content willMoveToParentViewController:nil];
// 步骤b
[content.view removeFromSuperview];
// 步骤c
[content removeFromParentViewController];
}
2.3 注意事项
- 调用
子VC
的removeFromParentViewController
方法会主动触发子VC
的didMoveToParentViewController:
方法被调用,参数为nil
三、处理childViewControllers
过渡动画
3.1 示例:
- (void)cycleFromViewController: (UIViewController*) oldVC
toViewController: (UIViewController*) newVC {
// 为两个VC过渡做准备
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// 获取新VC的初始frame和最终frame, 两个frame都是离屏的
newVC.view.frame = [self newViewStartFrame];
CGRect endFrame = [self oldViewEndFrame];
// 过渡动画入队列.
[self transitionFromViewController: oldVC toViewController: newVC
duration: 0.25 options:0
animations:^{
// 设置动画最终形态
newVC.view.frame = oldVC.view.frame;
oldVC.view.frame = endFrame;
}
completion:^(BOOL finished) {
// 最后,移除旧VC,并发通知给新VC
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
}];
}
四、管理childViewControllers
的外观变化周期
默认情况下,当
父VC
的生命周期变化时或添加子VC
和移除子VC
时, childViewControllers的viewWillAppear:
,viewDidAppear:
,viewWillDisappear:
和viewDidDisappear
会被主动触发调用
4.1 接管childViewControllers
的外观更新任务
重写父VC
的shouldAutomaticallyForwardAppearanceMethods
方法,返回NO
- (BOOL) shouldAutomaticallyForwardAppearanceMethods {
return NO;
}
4.2 自主转发外观变化消息
调用beginAppearanceTransition:animated:
方法开始外观更新,调用endAppearanceTransition
结束更新,示例:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear: animated];
[self.childViewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull child, NSUInteger idx, BOOL * _Nonnull stop) {
[child beginAppearanceTransition: YES animated: animated];
}];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear: animated];
[self.childViewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull child, NSUInteger idx, BOOL * _Nonnull stop) {
[child endAppearanceTransition];
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear: animated];
[self.childViewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull child, NSUInteger idx, BOOL * _Nonnull stop) {
[child beginAppearanceTransition: NO animated: animated];
}];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear: animated];
[self.childViewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull child, NSUInteger idx, BOOL * _Nonnull stop) {
[child endAppearanceTransition];
}];
}
4.3 添加和移除childViewControllers
的一些改变
以下两个示例分别参考
UINavigationController
进行push和pop时childViewController
生命周期的变化
4.3.1 添加childViewController
示例
// 参考UINavigationController push时的生命周期
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
// 该步骤将触发childViewController的viewWillAppear:方法被调用
[content beginAppearanceTransition: YES animated: YES];
// 添加子VC的根视图,可以添加一些转场动画效果
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
// 该步骤将触发childViewController的viewDidAppear:方法被调用
[content endAppearanceTransition];
[content didMoveToParentViewController:self];
}
4.3.2 移除childViewController
示例
// 参考UINavigationController pop时的生命周期
- (void) hideContentController: (UIViewController*) content {
[content willMoveToParentViewController:nil];
// 该步骤将触发childViewController的viewWillDisappear:方法被调用
[content beginAppearanceTransition: NO animated: YES];
// 添加一些转场动画效果并移除子VC的根布局
[content.view removeFromSuperview];
// 该步骤将触发childViewController的viewDidDisappear:方法被调用
[content endAppearanceTransition];
[content removeFromParentViewController];
}
五、将控制委派给子视图控制器
容器视图控制器可以将其自身外观的某些方面委托给其一个或多个子项。 您可以通过以下方式委派控制权:
- 重写
childViewControllerForStatusBarStyle
让子VC
控制状态栏样式 - 重写
childViewControllerForStatusBarHidden
让子VC
控制状态栏显示和隐藏 - 重写
preferredContentSize
让子VC
控制其期望的视图大小
六、关于自定义容器ViewController的建议
-
父VC
只跟子VC
的根布局打交道 -
子VC
应该对父VC
知之甚少,与父VC
交互尽量使用delegate模式
七、参考资料
- Implementing a Container View Controller