首页 > 其他 > 详细

Custom Container View Controller

时间:2015-09-23 16:52:59      阅读:301      评论:0      收藏:0      [点我收藏+]

什么是Container View Controller?苹果文档是这么描述的:

 A container view controller contains content owned by other view controllers.

 

也就是说一个View Controller显示的某部分内容属于另一个View Controller,那么这个View Controller就是一个Container,比如UIKit中的UINavigationController,UITabBarController。

 

在iOS 5之前苹果是不允许出现自定义的Container的 ,也就是说你创建的一个View Controller的view不能包含另一个View Controller的view,这对于逻辑复杂的界面来说,不易于功能拆分。也许曾经你为了某个公用的显示逻辑,直接将某个View Controller的view添加到另一个View Controller的view上,然后发现可以正常显示和使用,但实际上这种行为是非常危险的。

 

iOS 5.0 开始支持Custom Container View Controller,开放了用于构建自定义Container的接口。如果你想创建一个自己的Container,那么有一些概念还得弄清楚。Container的主要职责就是管理一个或多个Child View Controller的展示的生命周期,需要传递显示以及旋转相关的回调。

 

其实显示或者旋转的回调的触发的源头来自于window,一个app首先有一个主window,初始化的时候需要给这个主window指定一个rootViewController,window会将显示相关的回调(viewWillAppear:, viewWillDisappear:, viewDidAppear:, or viewDidDisappear: )以及旋转相关的回调(willRotateToInterfaceOrientation:duration: ,willAnimateRotationToInterfaceOrientation:duration:, didRotateFromInterfaceOrientation:)传递给rootViewController。rootViewController需要再将这些callbacks的调用传递给它的Child View Controllers。

 

一. 父子关系范式

实现一个Custom Container View Controller并不是一个简单的事情,主要分为两个阶段:父子关系的建立以及父子关系的解除。如果pVC将cVC的view添加为自己的subview,那么cVC必须为pVC的Child View Controller,而反过来则不一定成立,比如UINavigationController,一个View Controller被push进来后便和navigationController建立父子关系了,但是只有最上面的View Controller 是显示着的,底下的View Controller的view则被移出了容器的view的显示层级,当一个View Controller被pop之后,便和navigationController解除了父子关系了。

 

展示一个名为content的child view controller:

  1. [self addChildViewController:content];  //1 
  2. content.view.frame = [self frameForContentController];  
  3. [self.view addSubview:self.currentClientView]; //2 
  4. [content didMoveToParentViewController:self]; //3 

1.将content添加为child view controller,addChildViewController:接口建立了逻辑上的父子关系,子可以通过parentViewController,访问其父VC,addChildViewController:接口的逻辑中会自动调用 [content willMoveToParentViewController:self];

2.建立父子关系后,便是将content的view加入到父VC的view hierarchy上,同时要决定的是 content的view显示的区域范围。 

3.调用child的 didMoveToParentViewController: ,以通知child,完成了父子关系的建立

 

移除一个child view controller:

  1. [content willMoveToParentViewController:nil]; //1 
  2. [content.view removeFromSuperview]; //2 
  3. [content removeFromParentViewController]; //3 

1.通知child,即将解除父子关系,从语义上也可以看出 child的parent即将为nil

2.将child的view从父VC的view的hierarchy中移除 

3.通过removeFromParentViewController的调用真正的解除关系,removeFromParentViewController会自动调用 [content didMoveToParentViewController:nil]

 

二. appearance callbacks的传递

上面的实现中有一个问题,就是没看到那些appearance callbacks是如何传递的,答案就是appearance callbacks默认情况下是自动调用的,苹果框架底层帮你实现好了,也就是在上面的addSubview的时候,在subview真正加到父view之前,child的viewWillAppear将被调用,真正被add到父view之后,viewDidAppear会被调用。移除的过程中viewWillDisappear,viewDidDisappear的调用过程也是类似的。

 

有时候自动的appearance callbacks的调用并不能满足需求,比如child view的展示有一个动画的过程,这个时候我们并不想viewDidAppear的调用在addSubview的时候进行,而是等展示动画结束后再调用viewDidAppear。

 

也许你可能会提到 transitionFromViewController:toViewController:duration:options:animations:completion: 这个方法,会帮你自动处理view的add和remove,以及支持animations block,也能够保证在动画开始前调用willAppear或者willDisappear,在调用结束的时候调用didAppear,didDisappear,但是此方式也存在局限性,必须是两个新老子VC的切换,都不能为空,因为要保证新老VC拥有同一个parentViewController,且参数中的viewController不能是系统中的container,比如不能是UINavigationController或者UITabbarController等。

 

所以如果你要自己写一个界面容器往往用不了appearence callbacks自动调用的特性,需要将此特性关闭,然后自己去精确控制appearance callbacks的调用时机。

 

那如何关闭appearance callbacks的自动传递的特性呢?

 

在iOS 5.x中你需要覆盖automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers,然后返回NO,iOS6+中你需要覆盖 shouldAutomaticallyForwardAppearanceMethods方法并返回NO。

 

手动传递的时候你并不能直接去调用child 的viewWillAppear或者viewDidAppear这些方法,而是需要使用 beginAppearanceTransition:animated:和endAppearanceTransition接口来间接触发那些appearance callbacks,且begin和end必须成对出现。 

 

[content beginAppearanceTransition:YES animated:animated]触发content的viewWillAppear,[content beginAppearanceTransition:NO animated:animated]触发content的viewWillDisappear,和他们配套的[content endAppearanceTransition]分别触发viewDidAppear和viewDidDisappear。 (AppearanceTransition的这两个接口之前在苹果描述的文档中一开始还存在问题,因为文档中一开始说是iOS5不支持这两个接口,其实是支持的,后来苹果纠正了文档中的这个错误)。

 

三. rotation callbacks的传递

也许在iPhone上很少要关心的屏幕旋转问题的,但是大屏幕的iPad上就不同了,很多时候你需要关心横竖屏。rotation callbacks 一般情况下只需要关心三个方法 willRotateToInterfaceOrientation:duration:在旋转开始前,此方法会被调用;willAnimateRotationToInterfaceOrientation:duration: 此方法的调用在旋转动画block的内部,也就是说在此方法中的代码会作为旋转animation block的一部分;didRotateFromInterfaceOrientation:此方法会在旋转结束时被调用。而作为view controller container 就要肩负起旋转的决策以及旋转的callbacks的传递的责任。

 

当使用框架的自动传递的特性的时候,作为容器的view controller 会自动 将这些方法传递给所有的child viewcontrollers, 有时候你可能不需要传递给所有的child viewcontroller,而只需要传递给正在显示的child viewcontroller,那么你就需要禁掉旋转回调自动传递的特性,和禁掉appearance callbacks自动传递的方式类似,需要覆盖相关方法并返回NO,在iOS5.x中,appearance callbacks和rotation callbacks禁掉是公用一个方法的就是 automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers,在iOS6之后分成两个独立的方法,旋转的则是 shouldAutomaticallyForwardRotationMethods。 

 

旋转相关的除了上面的几个rotation callbacks方法外,还有一个十分重要的概念,就是一个view controller可以决定自己是否支持当前取向的旋转,这个东西在iOS6前后的实现方式还不一样,iOS6之前使用的方法是 shouldAutorotateToInterfaceOrientation,就是一个view controller覆盖此方法,根据传入的即将旋转的取向的参数,来决定是否旋转。而iOS6.0之后的实现则拆分成两个方法 shouldAutorotate和supportedInterfaceOrientations,前者决定再旋转的时候是否去根据supportedInterfaceOrientations所支持的取向来决定是否旋转,也就是说如果shouldAutorotate返回YES的时候,才会去调用supportedInterfaceOrientations检查当前view controller支持的取向,如果当前取向在支持的范围中,则进行旋转,如果不在则不旋转;而当shouldAutorotate返回NO的时候,则根本不会去管supportedInterfaceOrientations这个方法,反正是不会跟着设备旋转就是了。 

 

而作为界面容器你要注意的就是你需要去检查你的child view controller,检查他们对横竖屏的支持情况,以便容器自己决策在横竖屏旋转时候是否支持当前的取向,和上面的callbacks传递的方向相比,这其实是一个反向的传递。

 

四. 创建自己的容器基类

当你需要构建自己的Container View Controller的时候,每一个Container都会有一些相同的逻辑,如果你每一个都写一遍会存在很多重复代码,所以最好你创建一个Container基类,去实现容器都需要的逻辑。那到底有哪些逻辑是每一个Container都需要做的呢?关闭Appearance和Rotation相关方法的自动传递;当Container的Appearance和Rotation相关方法被调用时,需要将方法传递给相关的Child View Controller;以及当前Container是否支持旋转的决策逻辑等。下面为一个容器基类的示范: 

  1. #import "ContainerBaseController.h" 
  2.  
  3. @implementation ContainerBaseController 
  4.  
  5. #pragma mark - 
  6. #pragma mark Overrides 
  7. //NS_DEPRECATED_IOS(5_0,6_0) 
  8. - (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{ 
  9. return NO; 
  10. } 
  11.  
  12. //NS_AVAILABLE_IOS(6_0) 
  13. - (BOOL)shouldAutomaticallyForwardAppearanceMethods{ 
  14.     return NO; 
  15. } 
  16.  
  17. //NS_AVAILABLE_IOS(6_0) 
  18. - (BOOL)shouldAutomaticallyForwardRotationMethods{ 
  19. return NO; 
  20. } 
  21.  
  22. - (void)viewWillAppear:(BOOL)animated{ 
  23.     [super viewWillAppear:animated]; 
  24.  
  25.     NSArray *viewControllers = [self childViewControllersWithAppearanceCallbackAutoForward]; 
  26.     for (UIViewController *viewController in viewControllers) { 
  27.         [viewController beginAppearanceTransition:YES animated:animated]; 
  28.     } 
  29. } 
  30.  
  31. - (void)viewDidAppear:(BOOL)animated{ 
  32.     [super viewDidAppear:animated]; 
  33.  
  34.     NSArray *viewControllers = [self childViewControllersWithAppearanceCallbackAutoForward]; 
  35.     for (UIViewController *viewController in viewControllers) { 
  36.         [viewController endAppearanceTransition]; 
  37.     } 
  38. } 
  39.  
  40. - (void)viewWillDisappear:(BOOL)animated{ 
  41.     [super viewWillDisappear:animated]; 
  42.  
  43.     NSArray *viewControllers = [self childViewControllersWithAppearanceCallbackAutoForward]; 
  44.     for (UIViewController *viewController in viewControllers) { 
  45.         [viewController beginAppearanceTransition:NO animated:animated]; 
  46.     } 
  47. } 
  48.  
  49. - (void)viewDidDisappear:(BOOL)animated{ 
  50.     [super viewDidDisappear:animated]; 
  51.  
  52.     NSArray *viewControllers = [self childViewControllersWithAppearanceCallbackAutoForward]; 
  53.     for (UIViewController *viewController in viewControllers) { 
  54.         [viewController endAppearanceTransition]; 
  55.     } 
  56. } 
  57.  
  58.  
  59. - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{ 
  60.     [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; 
  61.  
  62.     NSArray *viewControllers = [self childViewControllersWithRotationCallbackAutoForward]; 
  63.     for (UIViewController *viewController in viewControllers) { 
  64.         [viewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; 
  65.  
  66. } 
  67. } 
  68.  
  69. - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{ 
  70.     [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; 
  71.  
  72.     NSArray *viewControllers = [self childViewControllersWithRotationCallbackAutoForward]; 
  73.     for (UIViewController *viewController in viewControllers) { 
  74.             [viewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; 
  75.     } 
  76. } 
  77.  
  78. - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{ 
  79.     [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; 
  80.  
  81.     NSArray *viewControllers = [self childViewControllersWithRotationCallbackAutoForward]; 
  82.     for (UIViewController *viewController in viewControllers) { 
  83.         [viewController didRotateFromInterfaceOrientation:fromInterfaceOrientation]; 
  84.     } 
  85. } 
  86.  
  87. /* 
  88.  NS_AVAILABLE_IOS(6_0)  
  89.  向下查看和旋转相关的ChildViewController的shouldAutorotate的值 
  90.  只有所有相关的子VC都支持Autorotate,才返回YES 
  91.  */ 
  92. - (BOOL)shouldAutorotate{ 
  93.     NSArray *viewControllers = [self childViewControllersWithRotationCallbackAutoForward]; 
  94.     BOOL shouldAutorotate = YES; 
  95.     for (UIViewController *viewController in viewControllers) { 
  96.         shouldAutorotate = shouldAutorotate &&  [viewController shouldAutorotate]; 
  97.     } 
  98.  
  99.     return shouldAutorotate; 
  100. } 
  101.  
  102. /* 
  103.  NS_AVAILABLE_IOS(6_0)  
  104.  此方法会在设备旋转且shouldAutorotate返回YES的时候才会被触发 
  105.  根据对应的所有支持的取向来决定是否需要旋转 
  106.  作为容器,支持的取向还决定于自己的相关子ViewControllers 
  107.  */ 
  108. - (NSUInteger)supportedInterfaceOrientations{ 
  109.     NSUInteger supportedInterfaceOrientations = UIInterfaceOrientationMaskAll; 
  110.  
  111.     NSArray *viewControllers = [self childViewControllersWithRotationCallbackAutoForward]; 
  112.     for (UIViewController *viewController in viewControllers) { 
  113.         supportedInterfaceOrientations = supportedInterfaceOrientations & [viewController supportedInterfaceOrientations]; 
  114.     } 
  115.  
  116.     return supportedInterfaceOrientations; 
  117. } 
  118.  
  119.  
  120. /* 
  121.  NS_DEPRECATED_IOS(2_0, 6_0) 6.0以下,设备旋转时,此方法会被调用 
  122.  用来决定是否要旋转 
  123.  */ 
  124. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{ 
  125.     BOOL shouldAutorotate = YES; 
  126.     NSArray *viewControllers = [self childViewControllersWithRotationCallbackAutoForward]; 
  127.     for (UIViewController *viewController in viewControllers) { 
  128.     shouldAutorotate = shouldAutorotate &&  [viewController shouldAutorotateToInterfaceOrientation:toInterfaceOrientation]; 
  129.     } 
  130.     return shouldAutorotate; 
  131. } 
  132.  
  133. #pragma mark - 
  134. #pragma mark 下面两个方法是在需要的情况下给基类覆盖用的,毕竟不是所有的容器都需要将相关方法传递给所有的childViewControllers 
  135. - (NSArray *)childViewControllersWithAppearanceCallbackAutoForward{ 
  136.     return self.childViewControllers; 
  137. } 
  138.  
  139. - (NSArray *)childViewControllersWithRotationCallbackAutoForward{ 
  140.     return self.childViewControllers; 
  141. } 
  142.  
  143. @end 

  

五. 创建自己的Container

设计要点

创建一个Container,首先你得设计好Container View Controller的行为和公开的API,你可以好好参考UIKit中自带的一些Container的设计风格,比如UINaivgationController就是管理着一组Content View Controller的堆栈的Container,且正在显示的是栈顶的View Controller。

 

主要接口有View Controller的推入,此过程中viewController会和navigationController建立父子关系,并将viewController显示出来,如果animated是YES的话,则会有过场动画:

  1. - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated 

 

pop操作,移除栈顶的内容,会解除和navigationController的父子关系:

  1. - (UIViewController *)popViewControllerAnimated:(BOOL)animated; 

 

当然关于pop还有一些其他的便捷接口,这里就不赘述了。

 

另外需要提供一些快捷的接口方便获取特定的Child View Controller,比如topViewController可以获取栈顶的View Controller。

 

另外如有必要,Container还需要留有delegate接口,便于通知外面Container的相关行为阶段,便于外部做出相关操作,比如UINaivgationController就会在即将要push一个新的View Controller,已经push了一个新的View Controller等时机留有delegate方法。

 

还有一个需要考虑的问题就是直接或者间接的Child View Controller如何快速的检索到相应的Container呢?一般Container在实现的时候就需要考虑此问题并提供相应的接口,实现的方法一般就是实现一个UIViewController的Category,比如UINavigationController,在某个View Controller中访问其navigationController属性,会向上遍历,直到找到最近的类型为UINavigationController的祖先,如果找不到则为nil:

  1. @interface UIViewController (UINavigationControllerItem) 
  2. ... 
  3. @property(nonatomic,readonly,retain) UINavigationController *navigationController;  
  4. @end 

 

实现一个简单的模态窗口Container

模态展示 则至少存在present,dismiss的接口,以及获取模态View Controller的属性 :

  1. #import <UIKit/UIKit.h> 
  2. #import "ContainerBaseController.h" 
  3.  
  4. @interface SimpleModalContainerController : ContainerBaseController 
  5.  
  6. @property (nonatomic, readonly) UIViewController *simpleModalViewController; 
  7.  
  8. - (void)presentSimpleModalViewController:(UIViewController *)viewControllerToPresent 
  9.                             animated:(BOOL)animated; 
  10.  
  11. - (void)dismissSimpleModalViewControllerAnimated:(BOOL)animated; 
  12.  
  13. @end 
  14.  
  15. //实现如下 
  16. #import "SimpleModalContainerController.h" 
  17.  
  18. @interface SimpleModalContainerController () 
  19. @property (nonatomic, readwrite) UIViewController *simpleModalViewController; 
  20. @property (nonatomic, strong) UIButton *backgroundButton; 
  21. @end 
  22.  
  23. @implementation SimpleModalContainerController 
  24.  
  25. - (void)buttonTapped:(id)sender{ 
  26.     [self dismissSimpleModalViewControllerAnimated:YES]; 
  27. } 
  28.  
  29. - (UIButton *)backgroundButton{ 
  30.     if (!_backgroundButton) { 
  31.         _backgroundButton = [UIButton buttonWithType:UIButtonTypeCustom]; 
  32.         _backgroundButton.backgroundColor = [UIColor blackColor]; 
  33.         _backgroundButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 
  34.         _backgroundButton.alpha = 0.3; 
  35.         [_backgroundButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; 
  36.  
  37.     } 
  38.     _backgroundButton.frame = self.view.bounds; 
  39.     return _backgroundButton; 
  40. } 
  41.  
  42. - (void)presentSimpleModalViewController:(UIViewController *)viewControllerToPresent 
  43.                             animated:(BOOL)animated{ 
  44.     if (!self.simpleModalViewController && viewControllerToPresent) { 
  45.         self.simpleModalViewController = viewControllerToPresent; 
  46.  
  47.         [self addChildViewController:viewControllerToPresent]; 
  48.  
  49.         [viewControllerToPresent beginAppearanceTransition:YES animated:animated]; 
  50.  
  51.         [self.view addSubview:self.backgroundButton]; 
  52.  
  53.         viewControllerToPresent.view.center = CGPointMake(CGRectGetWidth(self.view.bounds) / 2.0, CGRectGetHeight(self.view.bounds) / 2.0); 
  54.         [self.view addSubview:viewControllerToPresent.view]; 
  55.  
  56.         if (animated) { 
  57.             viewControllerToPresent.view.alpha = 0; 
  58.             self.backgroundButton.alpha = 0; 
  59.  
  60.             [UIView animateWithDuration:0.3 animations:^{ 
  61.                 viewControllerToPresent.view.alpha = 1; 
  62.                 self.backgroundButton.alpha = 0.3; 
  63.             } completion:^(BOOL finished) { 
  64.                 [viewControllerToPresent endAppearanceTransition]; 
  65.                 [viewControllerToPresent didMoveToParentViewController:self]; 
  66.             }]; 
  67.         } else { 
  68.             self.backgroundButton.alpha = 0.3; 
  69.             [viewControllerToPresent endAppearanceTransition]; 
  70.             [viewControllerToPresent didMoveToParentViewController:self]; 
  71.         } 
  72.  
  73.     } 
  74. } 
  75.  
  76. - (void)dismissSimpleModalViewControllerAnimated:(BOOL)animated{ 
  77.     if (self.simpleModalViewController) { 
  78.         [self.simpleModalViewController willMoveToParentViewController:nil]; 
  79.         [self.simpleModalViewController beginAppearanceTransition:NO animated:animated]; 
  80.  
  81.         if (animated) { 
  82.             [UIView animateWithDuration:0.3 animations:^{ 
  83.                 self.backgroundButton.alpha = 0; 
  84.                 self.simpleModalViewController.view.alpha = 0 ; 
  85.             } completion:^(BOOL finished) { 
  86.                 [self.backgroundButton removeFromSuperview]; 
  87.  
  88.                 [self.simpleModalViewController.view removeFromSuperview]; 
  89.                 self.simpleModalViewController.view.alpha = 1.0; 
  90.                 [self.simpleModalViewController endAppearanceTransition]; 
  91.                 [self.simpleModalViewController removeFromParentViewController]; 
  92.                 self.simpleModalViewController = nil; 
  93.             }]; 
  94.         } else { 
  95.             [self.backgroundButton removeFromSuperview]; 
  96.  
  97.             [self.simpleModalViewController.view removeFromSuperview]; 
  98.             self.simpleModalViewController.view.alpha = 1.0; 
  99.             [self.simpleModalViewController endAppearanceTransition]; 
  100.             [self.simpleModalViewController removeFromParentViewController]; 
  101.             self.simpleModalViewController = nil; 
  102.         } 
  103.     } 
  104. } 
  105.  
  106. @end 

  

UIViewController的Category用于Child View Controller 获取上层的SimpleModalContainerController :

  1. @interface UIViewController (SimpleModalContainerController) 
  2.  
  3. @property (nonatomic, readonly) SimpleModalContainerController *simpleModalContainerController; 
  4.  
  5. @end 
  6.  
  7. @implementation UIViewController (SimpleModalContainerController) 
  8.  
  9. - (SimpleModalContainerController *)simpleModalContainerController{ 
  10.     for (UIViewController *viewController = self.parentViewController; viewController != nil; viewController = viewController.parentViewController) { 
  11.         if ([viewController isKindOfClass:[SimpleModalContainerController class]]) { 
  12.             return (SimpleModalContainerController *)viewController; 
  13.         } 
  14.     } 
  15.     return nil; 
  16. } 
  17.  
  18. @end 

Custom Container View Controller

原文:http://www.cnblogs.com/spiderdzl/p/4832537.html

踩
(0)
赞
(0)
   
举报
评论 一句话评论(0)
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!