在 iOS 5 之前,view controller 容器是 Apple 的特权。实际上,在 view controller 编程指南中还有一段申明,指出你不应该使用它们。Apple 对 view controllers 的总的建议曾经是“一个 view controller 管理一个全屏幕的内容”。这个建议后来被改为“一个 view controller 管理一个自包含的内容单元”。为什么 Apple 不想让我们构建自己的 tab bar controllers 和 navigation controllers?或者更确切地说,这段代码有什么问题:

[viewControllerA.view addSubView:viewControllerB.view]

UIWindow 作为作为一个应用程序的根视图(root view),是旋转和初始布局消息等事件产生的来源。在上图中,child view controller 的 view 插入到 root view controller 的视图层级中,被排除在这些事件之外了。View 事件方法诸如 viewWillAppear:将不会被调用。

在 iOS 5 之前构建自定义的 view controller 容器时,要保存一个 child view controller 的引用,还要手动在 parent view controller 中转发所有 view 事件方法的调用,要做好非常困难。

一个例子

当你还是个孩子,在沙滩上玩时,你父母是否告诉过你,如果不停地用铲子挖,最后会到达美国?我父母就说过,我就做了个叫做Tunnel 的 demo 程序来验证这个说法。你可以 clone 这个 Github 代码库并运行这个程序,它有助于让你更容易理解示例代码。(剧透:从丹麦西部开始,挖穿地球,你会到达南太平洋的某个地方)

为了寻找对跖点,也称作相反的坐标,将拿着铲子的小孩四处移动,地图会告诉你对应的出口位置在哪里。点击雷达按钮,地图会翻转过来显示位置的名称。

屏幕上有两个 map view controllers。每个都需要控制地图的拖动,标注和更新。翻过来会显示两个新的 view controllers,用来检索地理位置。所有的 view controllers 都包含于一个 parent view controller 中,它持有它们的 views,并保证正确的布局和旋转行为。

Root view controller 有两个 container views。添加它们是为了让布局,以及 child view controllers 的 views 的动画做起来更容易,我们马上就可以看到。

- (void)viewDidLoad
{
[super viewDidLoad]; //Setup controllers
_startMapViewController = [RGMapViewController new];
[_startMapViewController setAnnotationImagePath:@"man"];
[self addChildViewController:_startMapViewController]; // 1
[topContainer addSubview:_startMapViewController.view]; // 2
[_startMapViewController didMoveToParentViewController:self]; // 3
[_startMapViewController addObserver:self
forKeyPath:@"currentLocation"
options:NSKeyValueObservingOptionNew
context:NULL]; _startGeoViewController = [RGGeoInfoViewController new]; // 4
}

我们实例化了 _startMapViewController,用来显示起始位置,并设置了用于标注的图像。

  1. _startMapViewcontroller 被添加成 root view controller 的一个 child。这会自动在 child 上调用willMoveToParentViewController: 方法。
  2. child 的 view 被添加成 container view 的 subview。
  3. child 被通知到它现在有一个 parent view controller。
  4. 用来显示地理位置的 child view controller 被实例化了,但是还没有被插入到任何 view 或 controller 层级中。

布局

Root view controller 定义了两个 container views,它决定了 child view controller 的大小。Child view controllers 不知道会被添加到哪个容器中,因此必须适应大小。

- (void) loadView
{
mapView = [MKMapView new];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[mapView setDelegate:self];
[mapView setMapType:MKMapTypeHybrid]; self.view = mapView;
}

现在,它们就会用 super view 的 bounds 来进行布局。这样增加了 child view controller 的可复用性;如果我们把它 push 到 navigation controller 的栈中,它仍然会正确地布局。

过场动画

Apple 已经针对 view controller 容器做了细致的 API,我们可以构造我们能想到的任何容器场景的动画。Apple 还提供了一个基于 block 的便利方法,来切换屏幕上的两个 controller views。方法 transitionFromViewController:toViewController:(...)已经为我们考虑了很多细节。

- (void) flipFromViewController:(UIViewController*) fromController
toViewController:(UIViewController*) toController
withDirection:(UIViewAnimationOptions) direction
{
toController.view.frame = fromController.view.bounds; // 1
[self addChildViewController:toController]; //
[fromController willMoveToParentViewController:nil]; // [self transitionFromViewController:fromController
toViewController:toController
duration:0.2
options:direction | UIViewAnimationOptionCurveEaseIn
animations:nil
completion:^(BOOL finished) { [toController didMoveToParentViewController:self]; // 2
[fromController removeFromParentViewController]; // 3
}];
}
  1. 在开始动画之前,我们把 toController 作为一个 child 进行添加,并通知 fromController 它将被移除。如果fromController 的 view 是容器 view 层级的一部分,它的 viewWillDisapear: 方法就会被调用。
  2. toController 被告知它有一个新的 parent,并且适当的 view 事件方法将被调用。
  3. fromController 被移除了。

这个为 view controller 过场动画而准备的便捷方法会自动把老的 view controller 换成新的 view controller。然而,如果你想实现自己的过场动画,并且希望一次只显示一个 view,你需要在老的 view 上调用 removeFromSuperview,并为新的 view 调用addSubview:。错误的调用次序通常会导致 UIViewControllerHierarchyInconsistency 警告。例如:在添加 view 之前调用didMoveToParentViewController: 就触发这个警告。

为了能使用 UIViewAnimationOptionTransitionFlipFromTop 动画,我们必须把 children's view 添加到我们的 view containers 里面,而不是 root view controller 的 view。否则动画将导致整个 root view 都翻转。

通信

View controllers 应该是可复用的、自包含的实体。Child view controllers 也不能违背这个经验法则。为了达到目的,parent view controller 应该只关心两个任务:布局 child view controller 的 root view,以及与 child view controller 暴露出来的 API 通信。它绝不应该去直接修改 child view tree 或其他内部状态。

Child view controller 应该包含管理它们自己的 view 树的必要逻辑,而不是把它们看作单纯呆板的 views。这样,就有了更清晰的关注点分离和更好的可复用性。

在示例程序 Tunnel 中,parent view controller 观察了 map view controllers 上的一个叫 currentLocation 的属性。

[_startMapViewController addObserver:self
forKeyPath:@"currentLocation"
options:NSKeyValueObservingOptionNew
context:NULL];

当这个属性跟着拿着铲子的小孩的移动而改变时,parent view controller 将新坐标的对跖点传递给另一个地图:

[oppositeController updateAnnotationLocation:[newLocation antipode]];

类似地,当你点击雷达按钮,parent view controller 给新的 child view controllers 设置待检索的坐标。

[_startGeoViewController setLocation:_startMapViewController.currentLocation];
[_targetGeoViewController setLocation:_targetMapViewController.currentLocation];

我们想要达到的目标和你选择的手段无关,从 child 到 parent view controller 消息传递的技术,不论是采用 KVO,通知,或者是委托模式,child view controller 都应该独立和可复用。在我们的例子中,我们可以将某个 child view controller 推入到一个 navigation 栈中,它仍然能够通过相同的 API 进行通信。

转自:http://objccn.io/issue-1-4/

View Controller容器的更多相关文章

  1. iOS Container View Controller

    一.UIViewController 做iOS开发的经常会和UIViewController打交道,从类名可知UIViewController属于MVC模型中的C(Controller),说的更具体点 ...

  2. View Controller Programming Guide for iOS---(一)---About View Controllers

    About View Controllers View controllers are a vital link between an app’s data and its visual appear ...

  3. iOS 容器控制器 (Container View Controller)

    iOS 容器控制器 (Container View Controller) 一个控制器包含其他一个或多个控制器,前者为容器控制器 (Container View Controller),后者为子控制器 ...

  4. iOS架构师之路:控制器(View Controller)瘦身设计

    前言 古老的MVC架构是容易被iOS开发者理解和接受的设计模式,但是由于iOS开发的项目功能越来越负责庞大,项目代码也随之不断壮大,MVC的模糊定义导致我们的业务开发工程师很容易把大量的代码写到视图控 ...

  5. 【IOS笔记】View Controller Basics

    View Controller Basics   视图控制器基础 Apps running on iOS–based devices have a limited amount of screen s ...

  6. View Controller 视图管理总结

    View controller是iOS中顶层的视图载体和控制器,它需要对view负责,管理view的生命周期,相关处室话以及交互事件,除此以外还需要为view提供合适的数据,以供view使用. Vie ...

  7. View Controller Transition:京东加购物车效果

    冬天已经过去了,阳光越来越暖洋洋的了.还记得上学的时候,老师总说"春天是播种的季节",而我还没在朋友圈许下什么愿望.一年了,不敢想象回首还能看到点什么,所以勇往直前.当被俗世所扰, ...

  8. 自己定义View Controller转换动画

    原文链接 : Introduction to Custom View Controller Transitions and Animations 原文作者 : joyce echessa 译文出自 : ...

  9. Swift:超炫的View Controller切换动画

    匿名社交应用Secret的开发者开发了一款叫做Ping的应用,用户可以他们感兴趣的话题的推送. Ping有一个很炫的东西,就是主界面和之间切换的动画做的非常的好.每次看到一个非常炫的动画,都不由得会想 ...

随机推荐

  1. 六. 异常处理4.try和catch的使用

    尽管由Java运行时系统提供的默认异常处理程序对于调试是很有用的,但通常你希望自己处理异常.这样做有两个好处.第一,它允许你修正错误.第二,它防止程序自动终止.大多数用户对于在程序终止运行和在无论何时 ...

  2. 第七章 android-UI组件

    一.本章目录 二.用户界面概述 1,用户界面简介 (1)系统和用户之间进行信息交换的媒介 2,设计手机用户界面应解决的问题 (1)需要界面设计和逻辑代码完全分离(布局和逻辑代码分开放) (2)根据不同 ...

  3. Android简单文件浏览器源代码 (转)

    Android简单文件浏览器源代码 (转) activity_main .xml <LinearLayout xmlns:android="http://schemas.android ...

  4. POJ 3694 Network (求桥,边双连通分支缩点,lca)

    Network Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 5619   Accepted: 1939 Descripti ...

  5. UBIFS

    转:http://www.armadeus.com/wiki/index.php?title=UBIFS This is a preliminary page dealing with the ins ...

  6. [置顶] kubernetes资源对象--limitranges

    概念 LimitRange(简称limits)基于namespace的资源管理,包括pod和container的最小.最大和default.defaultrequests等. 一旦创建limits,以 ...

  7. C和C++中函数运行时间测试

    //clock()函数为c中,捕捉从程序开始运行到clock运行的时间//时间单位为clock tick,即为时钟打点#include<iostream>#include<cmath ...

  8. as well as

    一.as well 用法: 1.as well常用作状语,作“又:也”解,相当于too或also,常位于句末,无须用逗号与句子分开.如: I am going to London and my sis ...

  9. 转:Spring mvc中@RequestMapping 6个基本用法小结

    Spring mvc中@RequestMapping 6个基本用法小结 发表于3年前(2013-02-17 19:58)   阅读(11698) | 评论(1) 13人收藏此文章, 我要收藏 赞3 4 ...

  10. S2:外观模式 Facade

    为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用. 什么时候使用:1,开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口.2,维护一个大 ...