最近碰到一個問題是發(fā)現(xiàn)當(dāng)移除一個subview的時候,viewWillDisappear被調(diào)用,但是viewDidDisappear卻沒有被調(diào)用,導(dǎo)致注冊的通知沒有被注銷,進(jìn)而引發(fā)一系列的錯誤調(diào)用。趁此機會理清了一下有關(guān)"Child View Controller"的一些概念以及正確的調(diào)用方法。
Child View Controller
官方手冊UIViewControler定義了 "View Controller"的主要職責(zé),包括:更新視圖內(nèi)容;響應(yīng)用戶操作;調(diào)整視圖尺寸以及控制頁面布局。
對于我們這個Case中使用孩子視圖控制器的行為在手冊中叫做"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事實上是為了更好的管理addSubView: 使得View與ViewController可以一一對應(yīng),可以使用多Controller從而避免混在一起,可以在暫時不需要顯示視圖的時候不載入,而等到需要的時候再在家,并且通過這個還可以更加方便的進(jìn)行視圖切換,減少代碼耦合。(于此對應(yīng),iOS5之前,即使不顯示,所有的view也都被加載在內(nèi)存中)
ViewController容器篇這篇文章的總結(jié)蠻好,介紹了addChildViewController相關(guān)的API的使用規(guī)則:
//添加
[self addChildViewController: _currentVC];
//[_currentVC willMoveToParentViewController: self];(自動調(diào)用 省略)
//[_currentVC didMoveToParentViewController: self]; (可省略)
//移除
[_currentVC willMoveToParentViewController: nil];
[_currentVC removeFromParentViewController];
//[_currentVC didMoveToParentViewController: nil]; (自動調(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)換時,不需要移除"Child View Controller",保持多個"Child View Controller",并在之間切換是這個功能的目的之一。需要注意的是如何使用"MoveTo",而這里有一個重要的益處就是不需要再手動管理addSubView僅需在第一次初始化時使用,后面轉(zhuǎn)換時不需要。
為了檢測這些方法和孩子視圖控制器之間的調(diào)用映射,寫了下面的代碼進(jìn)行了測試:
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é)說:添加或者刪除孩子視圖控制器的行為不會導(dǎo)致孩子視圖控制器的Action,而添加或者刪除視圖會直接導(dǎo)致相應(yīng)的Action,而要保證孩子視圖占用內(nèi)存被及時釋放,需要顯示注銷任何的引用。
Navigation Controller
UINavigationController事實上就是一個"Container View Controller",而它提供了"Push/Pop"兩個方法來方便的添加和移除"Child View Controller",可以直接翻譯成上面的添加移除代碼。
在"popViewController"模式下,是無法將最底層的"View Controller"也移除的,也就是至少保留一個:

如果希望去除所有的"Child View Controller",可以通過重置viewControllers實現(xiàn),另外一點需要注意的是,在這種情況下,僅僅最上層的那個會調(diào)用"viewWillDisappear/viewDidDisappear":

從這里也會發(fā)現(xiàn),我們是不需要手動調(diào)用removeFromSuperview的,這些可以自動完成。
引用
Apple UIViewControler
Custom Container View Controller
How to remove UIViewControllers from stack so ViewDidDisappear is called?