IOS addChildViewController

简介

苹果在 iOS5 中给 UIViewController 新增加的 5 方法以及一个属性:

@property(nonatomic,readonly) NSArray<__kindof UIViewController *> *childViewControllers NS_AVAILABLE_IOS(5_0); 

- (void)addChildViewController:(UIViewController *)childController NS_AVAILABLE_IOS(5_0);    

- (void)removeFromParentViewController NS_AVAILABLE_IOS(5_0);

- (void)willMoveToParentViewController:(nullable UIViewController *)parent NS_AVAILABLE_IOS(5_0);   

- (void)didMoveToParentViewController:(nullable UIViewController *)parent NS_AVAILABLE_IOS(5_0);

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(5_0);

今天就来探讨下这个新增方法如何使用。

用法及详解

我们的日常开发中常有这样一种需求,通过切换标签来切换不同的页面,如果在一个 controller 管理这些 view 的话,代码就显得耦合度过高,也与 Apple 的 MVC 模式不符合,Apple 推荐一个 controller 管理一个 view。 同样有人建议类似  [self.view addSubview:viewController.view]  的方式用另一个 controller 来控制,但是这样会产生一个新的问题:直接 add 进去的subView不在 viewController的 view hierarchy 内,事件不会正常传递,如:旋转、触摸等,属于危险操作违背CocoaTouch开发的设计MVC原则,viewController应该且只应该管理一个view hierarchy。同样我们也要考虑另一个问题:这些子 view 大多数不会一直处于界面上,只是在某些情况下才会出现,例如登陆失败的提示 view,上传附件成功的提示 view,网络失败的提示 view 等。但是虽然这些 view 很少出现,但是我们却常常一直把它们放在内存中。另外,当收到内存警告时,我们如何把这些 view 从 super view 中去掉?

addChildViewController 可以有效的解决这些问题,首先我们来看看怎么用:

[self addChildViewController:newVC];            //添加
//[newVC willMoveToParentViewController:self];

[self.view addSubview:newVC.view];        
// addChildViewController会调用[child         willMoveToParentViewController:self] ,但是不会调用didMoveToParentViewController,所以需要显示调用

[newVC didMoveToParentViewController:self];

1.将newVC添加到Controller的childViewController,建立父子关系。可以通过parentViewController访问newVC的父类,调用addChildViewController方法系统会自动调用willMoveToParentViewController:方法。

2.将newVC的view加到父类的view上去,当然还要确定view在父类view上的frame。

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

//移除
// removeFromParentViewController在移除child前不会调用[self willMoveToParentViewController:nil] ,所以需要显示调用

[oldVC willMoveToParentViewController:nil];
[oldVC.view removeFromSuperview];
[oldVC removeFromParentViewController];
//[oldVC didMoveToParentViewController:nil];

1.通知child,即将解除父子关系,设置 child的parent即将为nil。

2.将child的view从父类的view中移除 。

3.通过removeFromParentViewController的调用真正的解除关系,removeFromParentViewController会自动调用

[towCol didMoveToParentViewController:nil]。

使用上述方法可以很好的规避上面提到的两种问题:

1、解决了代码的高耦合

2、系统在收到内存警告的时候会回收一些并未显示的view,释放内存

3、这种方式add进入的view是属于当前view hierarchy内,可以正常传递各种事件。

4、在使用addChildViewController:还有一个比较高级的特性就是可以由自己选择控制childViewController的Appearance callbacks。

分解开看下:

当childViewController没有被加到任何父视图控制器时,如果把childViewController的view加到别的视图上,viewWillAppear和viewDidAppear会正常调用。但是当childViewController被加到一个父视图控制器上后,viewWillAppear和viewDidAppear就会与父视图控制器的viewWillAppear和viewDidAppear事件同步。

所以调用先后问题要注意:

先调用addSubView,viewWillAppear和viewDidAppear会各调用一次,再addChildViewController,与父视图控制器的事件同步,即当父视图控制器的viewDidAppear调用时,childViewController的viewDidAppear方法会再调用一次。所以viewDidAppear方法被调用了两次。

先调用addChildViewController,childViewController的事件与父视图控制器同步,当父视图控制器的viewDidAppear调用时,childViewController的viewDidAppear方法会调用一次,再调用addSubView也不会触发viewWillAppear和viewDidAppear。

如何管理子视图控制器的生命周期

willMoveToParentViewController:

//Called just before the view controller is added or removed from a container view controller.  在viewcontroller被添加或者移除之前时被调用;

didMoveToParentViewController:

//Called after the view controller is added or removed from a container view controller. 在viewcontroller被添加或者移除之后被调用。

为子控制器添加父视图控制器,父视图控制器就可以对其子控制器的生命周期、旋转特性进行管理。

同时也可以通过在父视图控制器中将 automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers 为 false , 那么系统不会自动管理子视图控制器的生命周期,使用 beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated 和 endAppearanceTransition 来处理。

当 isAppearing = YES 处理 willAppear 情况,endAppearanceTransition 来处理viewDidAppear。

当 isAppearing = NO 时,处理 willDissAppear, endAppearanceTransition 来处理viewDidDissAppear.

一个自定义的容器类视图控制器必须手动地发送willMoveToParentViewController: 和 didMoveToParentViewController:消息给它的子视图控制器,这样,子视图控制器才能自动地收到相关的视图控制器生命周期消息:viewDidAppear:,viewWillDisappear:...和设备旋转消息。但是,但是你也可以自己手动负责这些生命周期消息和旋转消息的发送,通过实现下面的方法:

shouldAutomaticallyForwardRotationMethods

如果你重写这个方法返回NO,那么你需要负责调用下面的方法给你的子视图控制器:

willRotateToInterfaceOrientation:duration:

willAnimateRotationToInterfaceOrientation:duration:

didRotateFromInterfaceOrientation:

shouldAutomaticallyForwardAppearanceMethods

如果你重写这个方法返回NO,那么你需要负责调用下面的方法给你的子视图控制器:

viewWillAppear:

viewDidAppear:

viewWillDisappear:

viewDidDisappear:

但是你iOS 6 和iOS 7中,你不会直接调用这些方法,因为你不能确定那个合适的时候去调用它们,而你可以在子视图控制器上调用下面两个方法:

beginAppearanceTransition:animated:

第一个参数是BOOL类型,YES表示这个视图控制器将要显示,NO表示将要消失

endAppearanceTransition

这样你就可以去控制子控制器的生命周期和旋转消息的发送了。

稍后整理一个 Demo !



作者:TT_IT有故事的程序员
链接:https://www.jianshu.com/p/2148f9cfa010
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

猜你喜欢

转载自blog.csdn.net/u012160319/article/details/82690206
ios