前言
控制器容器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。
展示子控制器
[self addChildViewController:content]; //1
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView]; //2
[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,完成了父子關系的建立
移除子控制器
[content willMoveToParentViewController:nil]; //1
[content.view removeFromSuperview]; //2
[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等。
有時候并不想使用默認情況下的自動調用,那么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。
rotation callbacks的傳遞
也許在iPhone上很少要關心的屏幕旋轉問題的,但是大屏幕的iPad上就不同了,很多時候你需要關心橫豎屏。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,檢查他們對橫豎屏的支持情況,以便容器自己決策在橫豎屏旋轉時候是否支持當前的取向,和上面的callbacks傳遞的方向相比,這其實是一個反向的傳遞。