iOS Child Controller

最近碰到一个问题是发现当移除一个subview的时候,viewWillDisappear被调用,但是viewDidDisappear却没有被调用,导致注册的通知没有被注销,进而引发一系列的错误调用。趁此机会理清了一下有关"Child View Controller"的一些概念以及正确的调用方法。

Child View Controller

官方手册UIViewControler定义了 "View Controller"的主要职责,包括:更新视图内容;响应用户操作;调整视图尺寸以及控制页面布局。

对于我们这个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: 使得ViewViewController可以一一对应,可以使用多Controller从而避免混在一起,可以在暂时不需要显示视图的时候不载入,而等到需要的时候再在家,并且通过这个还可以更加方便的进行视图切换,减少代码耦合。(于此对应,iOS5之前,即使不显示,所有的view也都被加载在内存中)

ViewController容器篇这篇文章的总结蛮好,介绍了addChildViewController相关的API的使用规则:

//添加
[self addChildViewController: _currentVC];
//[_currentVC willMoveToParentViewController: self];(自动调用  省略)
//[_currentVC didMoveToParentViewController: self]; (可省略)

//移除
[_currentVC willMoveToParentViewController: nil];
[_currentVC removeFromParentViewController];
//[_currentVC didMoveToParentViewController: nil]; (自动调用 省略)

//转换
[_currentVC willMoveToParentViewController: nil];
[self transitionFromViewController: _currentVC toViewController: _secondVC];
[_secondVC didMoveToParentViewController: self];

//转换子视图控制器
- (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;
        }
    }];
}

使用转换时,不需要移除"Child View Controller",保持多个"Child View Controller",并在之间切换是这个功能的目的之一。需要注意的是如何使用"MoveTo",而这里有一个重要的益处就是不需要再手动管理addSubView仅需在第一次初始化时使用,后面转换时不需要。

为了检测这些方法和孩子视图控制器之间的调用映射,写了下面的代码进行了测试:

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, 不导致调用
        self.view.addSubview(currentViewController.view)//导致调用 viewWillAppear/viewDidAppear
        currentViewController.didMove(toParentViewController: self)
    }
}
    
func clickAction() {
    if let currentViewController = viewControllerOne {
        currentViewController.willMove(toParentViewController: nil)
        currentViewController.view.removeFromSuperview()//导致调用 viewWillDisappear/viewDidDisappear
        currentViewController.removeFromParentViewController()//移出self.childViewControllers, 不导致调用
        viewControllerOne = nil//导致调用 deinit
    }
}

可以总结说:添加或者删除孩子视图控制器的行为不会导致孩子视图控制器的Action,而添加或者删除视图会直接导致相应的Action,而要保证孩子视图占用内存被及时释放,需要显示注销任何的引用。

Navigation Controller

UINavigationController事实上就是一个"Container View Controller",而它提供了"Push/Pop"两个方法来方便的添加和移除"Child View Controller",可以直接翻译成上面的添加移除代码。

在"popViewController"模式下,是无法将最底层的"View Controller"也移除的,也就是至少保留一个:


iOS Child Controller_第1张图片

如果希望去除所有的"Child View Controller",可以通过重置viewControllers实现,另外一点需要注意的是,在这种情况下,仅仅最上层的那个会调用"viewWillDisappear/viewDidDisappear":

iOS Child Controller_第2张图片

从这里也会发现,我们是不需要手动调用removeFromSuperview的,这些可以自动完成。

引用

Apple UIViewControler

ViewController容器篇

iOS addChildViewController方法

Custom Container View Controller

唐巧iOS5中UIViewController的新方法

How to remove UIViewControllers from stack so ViewDidDisappear is called?

iOS UINavigationController里的push和pop操作、抽屉效果原理

你可能感兴趣的:(iOS Child Controller)