addChildViewController方法详解

作者: TT_IT有故事的程序员 | 来源:发表于2018-03-27 12:09 被阅读6679次

    简介

    苹果在 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 !

    相关文章

      网友评论

        本文标题:addChildViewController方法详解

        本文链接:https://www.haomeiwen.com/subject/tldrcftx.html