现在越来越多的App都倾向于把标签栏放在顶部管理着多个页面。像今日头条的首页有十几个页面,如果同时存在这将是对内存的一次极大的消耗。自然而然就会想到用容器视图控制器去导航和管理,容器视图控制器也就是所说的父子视图控制器。
为了实现一个容器视图控制器,我们必须建立容器视图控制器和它的子视图控制器之间的关系。也就是所说的父子关系,只有建立了父子视图控制器关系,才能够对子视图控制器和其视图进行管理。那么接下来看看具体如何进行添加子视图控制器和去除子视图控制器
添加子视图控制器
为父视图控制器添加子视图控制器,我们需要进行如下操作:
1)调用容器视图控制器的addChildViewController:,此方法是将子视图控制器添加到容器视图控制器,告诉UIKit父视图控制器现在要管理子视图控制器和它的视图。
2)调用 addSubview: 方法,将子视图控制器的根视图加在父视图控制器的视图层级上。这里需要设置子视图控制器中根视图的位置和大小。
3)布局子视图控制器的根视图。
4)调用 didMoveToParentViewController:,告诉子视图控制器其根视图的被持有情况。也就是需要先把子视图控制器的根视图添加在父视图中的视图层级中。
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
}
- 在调用 addChildViewController: 时,系统会先调用 willMoveToParentViewController: 然后再将子视图控制器添加到父视图控制器中。但是系统不会自动调用 didMoveToParentViewController: 方法需要手动调用,为何呢?视图控制器是有转场动画的,动画完成后才应该去调用 didMoveToParentViewController: 方法。
移除子视图控制器
为了从容器视图控制器中去除子视图控制器,我们需要进行如下操作:
1)调用子视图控制器的willMoveToParentViewController:,参数为 nil,让子视图做好被移除的准备。
2)移除子视图控制器的根视图在添加时所作的任何的约束布局。
3)调用 removeFromSuperview 将子视图控制器的根视图从视图层次结构中移除。
4)调用 removeFromParentViewController 来告知结束父子关系。
5)在调用 removeFromParentViewController 时会调用子视图控制器的 didMoveToParentViewController: 方法,参数为 nil。
- (void) hideContentController: (UIViewController*) content {
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
子视图控制器之间的过渡动画
当需要子视图控制器过渡到另一个视图控制器的动画,结合子视图控制器的添加和删除到过渡动画过程。在动画之前,确保两个子视图控制器是内容的一部分,让当前的子视图消失。在动画过程中,移动新子视图控制器到相应的位置并删除旧的子视图控制器。在动画完成之后,删除子视图控制器。
- (void)cycleFromViewController: (UIViewController*) oldVC toViewController: (UIViewController*) newVC {
// Prepare the two view controllers for the change.
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Get the start frame of the new view controller and the end frame
// for the old view controller. Both rectangles are offscreen.
newVC.view.frame = [self newViewStartFrame];
CGRect endFrame = [self oldViewEndFrame];
// Queue up the transition animation.
[self transitionFromViewController: oldVC toViewController: newVC
duration: 0.25 options:0
animations:^{
// Animate the views to their final positions.
newVC.view.frame = oldVC.view.frame;
oldVC.view.frame = endFrame;
}
completion:^(BOOL finished) {
// Remove the old view controller and send the final
// notification to the new view controller.
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
}];
}
由上面代码可知,上面的方法里并没有将子视图添加到父视图中,也没有相对于的移除工作,这是因为 transitionFromViewController:toViewController:duration:options:animations:completion: 会自动添加新的视图和移除之前的视图。以上自定义容器视图控制部分内容全部来自官方介绍,具体看这里
有了以上知识点,下面我们看一个具体的Demo
具体需求:点击segmentControl切换到不同的子视图控制器,子视图控制器对应的视图颜色分别是红、黄、蓝,具体代码如下,非常简单:
class ViewController: UIViewController {
var vcs: [UIViewController] = []
var colors = [UIColor.red, UIColor.yellow, UIColor.blue]
@IBOutlet var segmentControl: UISegmentedControl?
override func viewDidLoad() {
super.viewDidLoad()
addChildViewControllers()
// 默认选中第一个子视图控制器
transitionToChildViewController(0)
segmentControl?.addTarget(self, action: #selector(segmentControlTapped(_:)), for: .valueChanged)
}
@objc func segmentControlTapped(_ segment: UISegmentedControl) {
transitionToChildViewController(segment.selectedSegmentIndex)
}
// 创建子视图控制器,设置对应的背景颜色
func addChildViewControllers() {
colors.forEach { [weak self] color in
let child = ChildViewController()
child.view.backgroundColor = color
self?.vcs.append(child)
}
}
func transitionToChildViewController(_ index: Int) {
// 获取当前显示的视图控制器
let fromViewController = childViewControllers.count > 0 ? childViewControllers[0] : nil
// 获取即将显示的视图控制器
let toViewController = vcs[index]
// 如果点击的是同一个视图控制器不进行操作
if toViewController == fromViewController { return }
let toView: UIView = toViewController.view
toView.frame = self.view.frame
fromViewController?.willMove(toParentViewController: nil)
self.addChildViewController(toViewController)
self.view.addSubview(toView)
fromViewController?.view.removeFromSuperview()
fromViewController?.removeFromParentViewController()
toViewController.didMove(toParentViewController: self)
}
}
实现效果: