自定义容器视图控制器总结
同时发布于知乎
1.Apple文档对容器视图器的解释
容器视图控制器是将来自多个视图控制器的内容合并到单个用户界面中的一种方法。容器视图控制器通常用于导航,并基于现有内容创建新的用户界面类型。UIKit中的容器视图控制器的例子包括UINavigationController,UITabBarController和UISplitViewController,所有这些都方便您的用户界面的不同部分之间进行交互.
2.设计自定义容器视图控制器
在设计自己的容器视图控制器时,明确容器角色以及容器和包含的视图控制器之间的关系。在设计过程中,思考如下问题:
1.容器是什么角色?承担哪些职责?
2.有哪些Chil-VC? 他们之间是什么关系?
3.子视图控制器如何添加到容器或从容器中移除?4.需要手动控制child-vc生命周期吗? Apprease
5.过度动画如何设计?
6.需要支持转屏吗?
7.容器与Child VC 以及 Child VC 间 ,他们该如何通信
3.容器相关知识点
3.1掌握 UIViewController 生命周期
3.2 添加子视图控制器
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
}
解释:
1)调用容器视图控制器的addChildViewController:,此方法是将子视图控制器添加到容器视图控制器,告诉UIKit父视图控制器现在要管理子视图控制器和它的视图。
2)调用 addSubview: 方法,将子视图控制器的根视图加在父视图控制器的视图层级上。这里需要设置子视图控制器中根视图的位置和大小。
3)布局子视图控制器的根视图。
4)调用 didMoveToParentViewController:,告诉子视图控制器其根视图的被持有情况。也就是需要先把子视图控制器的根视图添加在父视图中的视图层级中。
在调用 addChildViewController: 时,系统会先调用 willMoveToParentViewController:
然后再将子视图控制器添加到父视图控制器中。
但是系统不会自动调用 didMoveToParentViewController: 方法需要手动调用
为何呢?
视图控制器是有转场动画的,动画完成后才应该去调用 didMoveToParentViewController: 方法。
3.3 移除子视图控制器
- (void) hideContentController: (UIViewController*) content {
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
解释:
1)调用子视图控制器的willMoveToParentViewController:,参数为 nil,让子视图做好被移除的准备。
2)移除子视图控制器的根视图在添加时所作的任何的约束布局。
3)调用 removeFromSuperview 将子视图控制器的根视图从视图层次结构中移除。
4)调用 removeFromParentViewController 来告知结束父子关系。
5)在调用 removeFromParentViewController 时会调用子视图控制器的 didMoveToParentViewController: 方法,参数为 nil。
3.4 子视图控制器之间的过渡动画
-(void)transitionFromViewController:(UIViewController *)fromViewController
toViewController:(UIViewController *)toViewController
duration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion;
*Note
函数内部系统会会自动添加新的视图和移除之前的视图
具体事例:
基本原理:
当需要子视图控制器过渡到另一个视图控制器的动画,结合子视图控制器的添加和删除到过渡动画过程。在动画之前,确保两个子视图控制器是内容的一部分,让当前的子视图消失。在动画过程中,移动新子视图控制器到相应的位置并删除旧的子视图控制器。在动画完成之后,删除子视图控制器。
// Prepare the two view controllers for the change.
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Get the start frame of the new view controller and the end frame
// for the old view controller. Both rectangles are offscreen.
newVC.view.frame = [self newViewStartFrame];
CGRect endFrame = [self oldViewEndFrame];
// Queue up the transition animation.
[self transitionFromViewController: oldVC toViewController: newVC
duration: 0.25 options:0
animations:^{
// Animate the views to their final positions.
newVC.view.frame = oldVC.view.frame;
oldVC.view.frame = endFrame;
}
completion:^(BOOL finished) {
// Remove the old view controller and send the final
// notification to the new view controller.
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
}];
上面的事例有一个缺陷:无法结合Autolayout来布局,原因
// XXX We can't add constraints here because the view is not yet in the view hierarchy
// animation setup
代码二次改造:
- (void) performTransitionFromViewController:(UIViewController*)fromVc toViewController:(UIViewController*)toVc {
[fromVc willMoveToParentViewController:nil];
[self addChildViewController:toVc];
UIView *toView = toVc.view;
UIView *fromView = fromVc.view;
[self.containerView addSubview:toView];
// TODO: set initial layout constraints here
[self.containerView layoutIfNeeded];
[UIView animateWithDuration:.25
delay:0
options:0
animations:^{
// TODO: set final layout constraints here
[self.containerView layoutIfNeeded];
} completion:^(BOOL finished) {
[toVc didMoveToParentViewController:self];
[fromView removeFromSuperview];
[fromVc removeFromParentViewController];
}];
}
3.5 appearance callbacks的传递
////设置为NO,屏蔽对childViewController的生命周期函数的自动调用,改为手动控制
- (BOOL)shouldAutomaticallyForwardAppearanceMethods {
return NO;
}
容器控制器就要在子控制出现和消失时通知子控制器,
分别通过调用子控制器的
beginAppearanceTransition:animated:
&&
endAppearanceTransition():
实现,不需要直接调用子控制器的
viewWillAppear:
viewDidAppear:
viewWillDisappear:
viewDidDisappear: 方法
另外注意的是:
begin和end必须成对出现
[content beginAppearanceTransition:YES animated:animated]触发content的viewWillAppear,
[content beginAppearanceTransition:NO animated:animated]触发content的viewWillDisappear,
和他们配套的[content endAppearanceTransition]
分别触发viewDidAppear和viewDidDisappear。
3.6 rotation callbacks的传递
rotation callbacks 一般情况下只需要关心三个方法 :
willRotateToInterfaceOrientation:duration:
在旋转开始前,此方法会被调用;
willAnimateRotationToInterfaceOrientation:duration:
此方法的调用在旋转动画block的内部,也就是说在此方法中的代码会作为旋转animation block的一部分;
didRotateFromInterfaceOrientation:
此方法会在旋转结束时被调用。
而作为view controller container 就要肩负起旋转的决策以及旋转的callbacks的传递的责任。
禁用方式:
禁掉默认调用需要重写两个方法
shouldAutorotate:
supportedInterfaceOrientations:
前者决定再旋转的时候是否去根据supportedInterfaceOrientations所支持的取向来决定是否旋转;
假如shouldAutorotate返回YES的时候,才会去调用supportedInterfaceOrientations检查当前view controller支持的取向,
如果当前取向在支持的范围中,则进行旋转,
如果不在则不旋转;
而当shouldAutorotate返回NO的时候,则根本不会去管supportedInterfaceOrientations这个方法,反正是不会跟着设备旋转就是了。
在编写的容器组过程中,先去检查你的child view controller对横竖屏的支持情况,以便容器自己决策在横竖屏旋转时候是否支持当前的取向
- (BOOL)shouldAutorotate {
UIViewController *visibleViewController ;
if (visibleViewController != self && [visibleViewController respondsToSelector:@selector(shouldAutorotate)]) {
return [visibleViewController shouldAutorotate];
}
return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
UIViewController *visibleViewController = ;
if (visibleViewController != self && [visibleViewController respondsToSelector:@selector(supportedInterfaceOrientations)]) {
return [visibleViewController supportedInterfaceOrientations];
}
return self.supportedOrientationMask;
}
3.6 其他
1.重载 childViewControllerForStatusBarStyle 属性,返回相应的子控制器,让子控制器决定状态栏样式。当这个属性发生变化,调用 setNeedsStatusBarAppearanceUpdate() 方法更新状态栏样式。
2.容器控制器可以用子控制器的 preferredContentSize 属性决定子控制器 view 的大小。