使用视图控制器容器(翻译自苹果官方文档 Implementing a Container View Controller)

原文地址

Container view controller 是一种将多个viewcontroller中的内容整合起来展现在一个单一的用户界面上的一种方式 .Container view controller最经常被用来方便管理(原文:navigation , 感觉理解为管理更恰当)和在已有的内容上创建新的界面类型 . 在UIKit框架中container view controller的例子包括:UINavigationController , UITabBarController , 和UISplitViewController , 它们全都是为了方便在你的界面的不同部分之间导航(navigation) . 

设计一个自定义的Container View Controller

总的来说 , 一个container view controller很像任何其他的content view controller因为它管理着一个root view和一些内容 . 不同之处在于:container view controller中的一部分东西是来自其他的view controllers , 从其它的view controller中获得的东西仅限于它们的views , 将这些views嵌入到自己的视图层中 . container view controller设置它所有的内嵌的view的size和position , 但是普通的view controller除此之外还管理这些view内的东西 . 

当你设计自己的container view controller的时候 , 你需要理解容器和被包含的view controller之间的联系 . view controllers之间的联系能够帮你知道它们的内容应该怎样显示在屏幕上和你的容器(container)内部到底是怎样管理它们的 . 在设计的的过程中 , 要问你自己下面几个问题:
-- 容器(container)和它的子控制器(children)分别扮演什么角色 ?   
-- 同时要展示多少个子控制器(children) ? 
-- 各个子控制器之间的联系是怎样的 ? 
-- 怎样将一个子控制器添加到容器(container)中和怎样将一个子控制器从容器中删除 ? 
-- 子控制器的视图的size和position能改变吗 ? 在什么条件下这些改变会发生 ? 
-- 在容器(container)和子控制器之间哪些联系是必须实现的 ? 除了UIViewController中定义的一些标准的事件 , 容器(container)需要将一些特殊的事件通知给它的子控制器吗 ? 
-- 容器(container)的显示(appearance)能够用不同的方式配置吗 ? 如果能,怎样做 ?

当你已经明确了各个对象的角色 , 去实现(implementation)一个container view controller是相对容易的 . UIKit唯一的要求是你必须在容器和所有子控制器之间确定条理清晰的联系 . 这种"父 - 子"关系确保子控制器能收到任何有关的系统消息 . 此外 , 对于每个容器(container)来说,在布局和管理被包含的views的过程中所做的实际的工作大部分是不相同的 . 你可以将views放置在你的容器(container)所包含区域的任何位置 , 设置任何你想要的尺寸 . 你也可以添加自定义视图到视图层用来提供修饰或者辅助导航 . 

实现一个自定义的Container View Controller

要实现一个container view controller,你必须先确定父控制器和它的子控制器之间的联系 . 在你尝试去管理任何子控制器的views之前 , 建立这种"父 - 子"关系式必须滴 . 这样做是为了让UIKit知道你的view controller正在管理着它的自控制器view的size和position . 你可以用Interface Builder或者代码的方式来建立这种联系 . 当用代码方式的时候 , 作为配置(setup)你view controller的一部分你要明确的添加或者删除子控制器 .  
添加一个子控制器到父控制器
按照如下步骤用代码包含一个子控制器到你的父控制器中 , 并在有关的控制器之间建立"父 - 子"关系 :
1 . 执行你的父控制器的addChildViewController: 方法 . 这个方法告诉UIKit你的父控制器正在管理子控制器的view . 
2 . 将子控制器的根视图添加到父控制器的视图层中 (还要记得设置子控制器的view的size和position) . 
3 . 为管理子控制器的size和position添加所有的约束 . 
4 . 执行子控制器的didMoveToParentViewController: 方法 . 

- (void) displayContentController: (UIViewController *) content {
[self addChildViewController: content];
content.view.frame = [self frameForContentController];
[self.view addSubview: content.view]; //此处官方文档为:[self.view addSubview:self.currentClientView]; 通过上下文个人感觉错了...
[content didMoveToParentViewController:self];
上面代码展示了如何嵌入一个子控制器到一个父控制器中 . 建立"父 - 子"关系之后 , 父控制器设置了子控制器view的frame然后把子控制器的view加到了自己的视图层中 . 
设置子控制器的view的frame很重要 , 确保这个view能够正确的在父控制器上显示 . 添加view之后 , 子控制器执行didMoveToParentViewController: 方法给子控制器一个机会去对视图的变化做出相应 . 
注意在上面的例子中 , 只执行了子控制器的didMoveToParentViewController: 方法  . 那是因为当父控制器执行addChildViewController: 方法的时候 , 已经为你通知子控制器执行了willMoveToParentViewController:方法 . 你必须亲自执行didMoveToParentViewController: 方法是因为这个方法只能在你将子控制器的view嵌入到父控制器的视图层之后执行 . 
当用自动布局的时候 , 在将子控制器的view嵌入到父控制器的视图层之后再设置父容器和子视图之间的约束 . 你的约束只能够影响子控制器的根视图 . 不要改变根视图上的内容或者子控制器视图层上的其他view . 

删除一个子控制器
从父控制器中删除一个子控制器 , 用如下步骤删除控制器之间的"父 - 子"关系 : 
1 . 子控制器执行willMoveToParentViewController: 方法 , 传参为nil . 
2 . 删除所有为子控制器的根视图添加的约束 . 
3 . 从父控制器的视图层中删除子控制器的根视图 . 
4 . 子控制器执行removeFromParentViewController方法结束"父 - 子"关系 . 
删除一个子控制器就永久性的断绝了"父 - 子"关系 . 只有在你不再需要一个子控制器的时候再去删除它 . 例如 , 一个导航控制器将要push一个新的子控制器的时候,不会删除当前子控制器 . 只在把一个子控制器pop出的时候才删除它 . 

- (void) hideContentController: (UIViewController *) content {
[content willMoveToParentViewController: nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}

上面代码展示了如何从父控制器删除一个子控制器 . 子控制器执行willMoveToParentViewController: 方法传参nil , 给自己一个机会做一些准备 . removeFromParentViewController方法中也执行了子控制器的didMoveToParentViewController: 方法 , 传参nil . 设置父控制器为nil最终敲定子控制器的view从父控制器上移除 . 

子控制器之间的转换
当你想用一个子控制器过渡替换另一个子控制器 , 把子控制器的添加和移除包含到过渡动画中 . 在动画之前 , 除了让当前的子控制器知道它将要离开 , 还要确保这两个控制器都是你的子控制器 . 动画过程中 , 将新的子控制器的view放到合适的位置和删除旧子控制器的view . 在动画完成的时候 , 完成旧子控制器的移除 . 
- (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 removeFromParentViewControoler];
[newVC didMoveToParentViewController:self];
}];
}
上面代码展示了怎样用动画效果为一个子控制器交换到另一个子控制器 . 在这个例子中 , 新子控制器的view移动到了旧子控制器的view正在占用的矩形中 , 旧的子控制器的view移动到屏幕外 . 动画完成之后 , 在completion block中从父控制器中删除旧子控制器 . 在这个例子中 , transitionFromViewController: toViewController: duration: options: animations: completion: 方法自动更新了父控制器的视图层 , 所以你不需要再手动添加或删除这些views .
 
管理子控制器的显示更新
添加一个子控制器之后 , 父控制器自动的会把显示有关的消息传递给子控制器 . 这是你想要的正常行为 , 因为这确保所有的事件被正确的发出 . 但是有时候一些违规的行为可能发送一些你的父控制器并不理解的事件 . 例如 , 多个子控制器在同时修改它们view的状态 , 你可能想要整合这些改变以便那些和显示有关的回调都能以一个更合理的逻辑同时调用 . 
为了接管显示相关的回调的响应 , 在你的父控制器中重写shouldAutomaticallyForwardAppearanceMethods方法并return NO (如下方代码展示) , return NO让UIKit知道在它显示的时候父控制器把改变通知给它的子控制器 . 
- (BOOL) shouldAutomaticallyForwardAppearanceMethods {
return NO;
}

当一个显示转换发生的时候 , 在恰当的时候调用子控制器的beginAppearanceTransition: animated: 或endAppearanceTransition方法 . 例如 , 当你的父控制器只有单独一个以"child"属性引用的子控制器的时候 , 父控制器将传递如下这些消息给子控制器 .
- (void) viewWillAppear: (BOOL) animated {
[self.child beginAppearanceTransition: YES animated: animated];
}
- (void) viewDidAppear: (BOOL) animated {
[self.child endAppearanceTransition];
}

- (void) viewWillDisappear: (BOOL) animated {
[self.child beginAppearanceTransition: NO animated: animated];
}

- (void) viewDidDisappear: (BOOL) animated {
[self.child endAppearanceTransition];
}

关于创建一个父控制器的建议  

设计 , 开发 , 测试一个新的父控制器需要时间 . 虽然有些个别的事情是简单的 , 这个控制器作为一个整体可以非常复杂 . 当你实现自己的父控制器类的时候请考虑一下技巧:
- 只要子控制器的根视图 . 决不能要子控制器的其他views . 
- 子控制器应该尽量最少的去了解它们的父控制器 . 一个子控制器应该集中注意在自己的内容上 . 如果父控制器一个子控制器去影响自己的行为 , 应该使用代理模式去管理这些活动 . 
- 先用简洁(regular)的视图设计你的父控制器 . 使用简洁(regular)视图(而不是来自子控制器的view)使得你可以在一个简单的环境中测试布局约束和动画过渡 . 当这个简洁的视图达到你预期的效果 , 再用字控制器中的view把它替换掉 . 

把操作委托给一个子控制器

一个父控制器可以把自己显示的一些方面委托给自己的一个或者多个子控制器 . 你可以通过以下方法委托代理 : 
- 让一个子控制器决定状态栏的样式 . 为了吧状态栏的外观委托给一个子控制器 , 重写父控制器中的childViewControllerForStatusBarStyle和childViewControllerForStatusBarHidden两个方法 . 
- 让子控制器指定自己喜欢的size . 一个布局灵活的父控制器可以用zi控制器的preferredContentSize属性去帮助决定子控制器的size . 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值