最近碰到一個(gè)問題是發(fā)現(xiàn)當(dāng)移除一個(gè)subview
的時(shí)候鉴未,viewWillDisappear
被調(diào)用裙盾,但是viewDidDisappear
卻沒有被調(diào)用薯鼠,導(dǎo)致注冊(cè)的通知沒有被注銷馋没,進(jìn)而引發(fā)一系列的錯(cuò)誤調(diào)用。趁此機(jī)會(huì)理清了一下有關(guān)"Child View Controller"的一些概念以及正確的調(diào)用方法袁波。
Child View Controller
官方手冊(cè)UIViewControler定義了 "View Controller"的主要職責(zé)瓦阐,包括:更新視圖內(nèi)容;響應(yīng)用戶操作篷牌;調(diào)整視圖尺寸以及控制頁(yè)面布局。
對(duì)于我們這個(gè)Case中使用孩子視圖控制器的行為在手冊(cè)中叫做"Container View Controller"踏幻,定義為:
A container view controller manages the presentation of content of other view controllers it owns, also known as its child view controllers
A child'??s view can be presented as-is or in conjunction with views owned by the container view controller.
引入addChildViewController
事實(shí)上是為了更好的管理addSubView
: 使得View
與ViewController
可以一一對(duì)應(yīng)枷颊,可以使用多Controller從而避免混在一起,可以在暫時(shí)不需要顯示視圖的時(shí)候不載入该面,而等到需要的時(shí)候再在家夭苗,并且通過這個(gè)還可以更加方便的進(jìn)行視圖切換,減少代碼耦合隔缀。(于此對(duì)應(yīng)题造,iOS5之前,即使不顯示猾瘸,所有的view也都被加載在內(nèi)存中)
ViewController容器篇這篇文章的總結(jié)蠻好界赔,介紹了addChildViewController
相關(guān)的API的使用規(guī)則:
//添加
[self addChildViewController: _currentVC];
//[_currentVC willMoveToParentViewController: self];(自動(dòng)調(diào)用 省略)
//[_currentVC didMoveToParentViewController: self]; (可省略)
//移除
[_currentVC willMoveToParentViewController: nil];
[_currentVC removeFromParentViewController];
//[_currentVC didMoveToParentViewController: nil]; (自動(dòng)調(diào)用 省略)
//轉(zhuǎn)換
[_currentVC willMoveToParentViewController: nil];
[self transitionFromViewController: _currentVC toViewController: _secondVC];
[_secondVC didMoveToParentViewController: self];
//轉(zhuǎn)換子視圖控制器
- (void)transitionFromOldViewController:(UIViewController *)oldViewController toNewViewController:(UIViewController *)newViewController{
[self transitionFromViewController:oldViewController toViewController:newViewController duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) {
if (finished) {
[newViewController didMoveToParentViewController:self];
_currentVC = newViewController;
}else{
_currentVC = oldViewController;
}
}];
}
使用轉(zhuǎn)換時(shí),不需要移除"Child View Controller"牵触,保持多個(gè)"Child View Controller"淮悼,并在之間切換是這個(gè)功能的目的之一。需要注意的是如何使用"MoveTo"揽思,而這里有一個(gè)重要的益處就是不需要再手動(dòng)管理addSubView僅需在第一次初始化時(shí)使用袜腥,后面轉(zhuǎn)換時(shí)不需要。
為了檢測(cè)這些方法和孩子視圖控制器之間的調(diào)用映射钉汗,寫了下面的代碼進(jìn)行了測(cè)試:
var viewControllerOne:TestOneViewController? = TestOneViewController()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(clickAction), name:NSNotification.Name.UIApplicationWillResignActive, object: nil)
if let currentViewController = viewControllerOne {
self.addChildViewController(currentViewController)//加入self.childViewControllers, 不導(dǎo)致調(diào)用
self.view.addSubview(currentViewController.view)//導(dǎo)致調(diào)用 viewWillAppear/viewDidAppear
currentViewController.didMove(toParentViewController: self)
}
}
func clickAction() {
if let currentViewController = viewControllerOne {
currentViewController.willMove(toParentViewController: nil)
currentViewController.view.removeFromSuperview()//導(dǎo)致調(diào)用 viewWillDisappear/viewDidDisappear
currentViewController.removeFromParentViewController()//移出self.childViewControllers, 不導(dǎo)致調(diào)用
viewControllerOne = nil//導(dǎo)致調(diào)用 deinit
}
}
可以總結(jié)說:添加或者刪除孩子視圖控制器的行為不會(huì)導(dǎo)致孩子視圖控制器的Action羹令,而添加或者刪除視圖會(huì)直接導(dǎo)致相應(yīng)的Action,而要保證孩子視圖占用內(nèi)存被及時(shí)釋放损痰,需要顯示注銷任何的引用福侈。
Navigation Controller
UINavigationController
事實(shí)上就是一個(gè)"Container View Controller",而它提供了"Push/Pop"兩個(gè)方法來方便的添加和移除"Child View Controller"徐钠,可以直接翻譯成上面的添加移除代碼癌刽。
在"popViewController"模式下,是無法將最底層的"View Controller"也移除的,也就是至少保留一個(gè):
如果希望去除所有的"Child View Controller"显拜,可以通過重置viewControllers
實(shí)現(xiàn)衡奥,另外一點(diǎn)需要注意的是,在這種情況下远荠,僅僅最上層的那個(gè)會(huì)調(diào)用"viewWillDisappear/viewDidDisappear":
從這里也會(huì)發(fā)現(xiàn)矮固,我們是不需要手動(dòng)調(diào)用removeFromSuperview
的,這些可以自動(dòng)完成譬淳。
引用
Apple UIViewControler
Custom Container View Controller
How to remove UIViewControllers from stack so ViewDidDisappear is called?