原文:http://www.thinkandbuild.it/working-with-custom-container-view-controllers/
我们将创建一个点击按钮弹出controller(点击按钮和弹出的次数没有限制)的应用。
下面简要说明如何实现
Container: 它继承自UIViewController,有一个subview属性,subview属性是其他子控制器和点击按钮后生成的新Detail Controller 共同的容器。
Detail: 它是一个简单的UIViewController,它包括一个标签和一个UIGestureRecognizer。在它的视图上滑动,标签的颜色会随机变化。此类的实例将成为容器视图控制器的子控制器。它的视图会赋值给容器的detailView,但是仍有自身管理,并接受手势事件。
源码:https://github.com/ariok/TB_ControllerContainment
添加子控制器
在正式开始之前,感谢FlatUI Kit (https://github.com/Grouper/FlatUIKit) 的作者Jack Flintermann.。FlatUI Kit 真是个特别炫酷的类库。
打开 ContainerViewController.m 找到presentDetailController 函数。这个函数完成新Detail controller加入容器视图控制器的操作。下面我们来看仔细代码
- (void)presentDetailController:(UIViewController*)detailVC{
//0. Remove the current Detail View Controller showed
if(self.currentDetailViewController){
[self removeCurrentDetailViewController];
}
//1. Add the detail controller as child of the container
[self addChildViewController:detailVC];
//2. Define the detail controller's view size
detailVC.view.frame = [self frameForDetailController];
//3. Add the Detail controller's view to the Container's detail view and save a reference to the detail View Controller
[self.detailView addSubview:detailVC.view];
self.currentDetailViewController = detailVC;
//4. Complete the add flow calling the function didMoveToParentViewController
[detailVC didMoveToParentViewController:self];
}
Step 0
此步骤我们一会儿再讨论,但这步的本质就是将当前正在显示的Detail ViewController 从容器中删除。
Step 1
IOS为了自定义容器控制器,加入了很多新的函数。addChildViewController 就是其中一个。就这么简单,我们在容器控制器中加了一个Detail Controller。
Step 2
新创建的Detail Controller的视图将要与容器控制器中预定义好的视图进行连接,所以我们要修改预定义视图的frame。
Step 3
现在,我们将Detail Controller的视图添加到容器的detailView上,并将新建的Detail Controller作为当前的Detail Controller。
Step 4
didMoveToParentViewController也是新加入UIViewController类的一个函数。调用这个函数后,我们发送给Detail Controller 实例一个消息,告诉它,它现在是其他控制器的子控制器。删除子控制器
现在,我们来看 removeCurrentDetailViewController (与上面函数在同一.m文件中)
Step 1
我们通过在willMoveToParentViewController 以nil 为参数,给current Detail Controller 发送一条信息,通知它将要从父控制器中移除。
Step 2
将current Detail controller的视图从父视图中移除。
Step 3
通过调用标准函数removeFromParentViewController,我们将current Detail Controlle从容器中移除。当这个函数被调用的时候,Detail Controller会自动调用 didMoveToParentViewController,调用时的参数为nil。
结果
第一个Detail Controller在容器的viewDidLoad函数中被创建。initWithString:withColor函数帮我们创建一个DetailViewController实例。我们调用presentDetailController 函数,将刚刚创建的实例作为当前的Detail Controller (current Detail Controller)。就像之前提到的,按钮在容器控制器的主视图上,当我们按下后,就展示一个新的Detail Controller。我们通过addDetailController来添加按钮事件,代码不多,如下:
- (IBAction)addDetailController:(id)sender {
DetailViewController *detailVC = [[DetailViewController alloc]initWithString:@"This is a new viewController!" withColor:[UIColor asbestosColor]];
/* Mode 1 */
[self presentDetailController:detailVC];
/* Mode 2 */
//[self swapCurrentControllerWith:detailVC];
}
添加动画效果
还没有提到动画,下面说说。当我们push一个视图控制器的时候可以加入不同的动画,是个很好的想法。我们不去分别创建方法来添加或删除Detail Controllers,而是将所有操作都放在一个名为swapCurrentControllerWith的函数,我们将新建的ViewController做为参数传入,下面是完整代码:
- (void)swapCurrentControllerWith:(UIViewController*)viewController{
//1. The current controller is going to be removed
[self.currentDetailViewController willMoveToParentViewController:nil];
//2. The new controller is a new child of the container
[self addChildViewController:viewController];
//3. Setup the new controller's frame depending on the animation you want to obtain
viewController.view.frame = CGRectMake(0, 2000, viewController.view.frame.size.width, viewController.view.frame.size.height);
//The transition automatically removes the old view from the superview and attaches the new controller's view as child of the
//container controller's view
//Save the button position... we'll need it later
CGPoint buttonCenter = self.button.center;
[self transitionFromViewController:self.currentDetailViewController toViewController:viewController
duration:1.3 options:0
animations:^{
//The new controller's view is going to take the position of the current controller's view
viewController.view.frame = self.currentDetailViewController.view.frame;
//The current controller's view will be moved outside the window
self.currentDetailViewController.view.frame = CGRectMake(0,
-2000,
self.currentDetailViewController.view.frame.size.width,
self.currentDetailViewController.view.frame.size.width);
self.button.center = CGPointMake(buttonCenter.x,1000);
} completion:^(BOOL finished) {
//Remove the old view controller
[self.currentDetailViewController removeFromParentViewController];
//Set the new view controller as current
self.currentDetailViewController = viewController;
[self.currentDetailViewController didMoveToParentViewController:self];
//reset the button position
[UIView animateWithDuration:0.5 animations:^{
self.button.center = buttonCenter;
}];
}];
}
结论
如果你想添加一个新的子控制器并且删除之前的,按照以下步骤来做,这是每种导航模式里(navigation patterns 比如Navigation 、TabBar)共同的。
1 Current detail controller: [current willMoveToParentViewController:nil] //当前的控制器
2 Next detail controller: [container addChildViewController:next] //下一个控制器
2.B Next detail controller: [next willMoveToParentViewController:self] (自动调用2)
3 Next detail controller: [container.view addSubview:next.view]
4 Current detail controller: [current.view removeFromSuperView]
5 Current detail controller: [current removeFromParentViewController]
6 Current detail controller: [current didMoveToParentViewController:nil] (自动被5调用)
7 Next detail controller: [next didMoveToParentViewController:self]
你可以利用这些新加入的函数来创建自己的导航模式。显然,UINavigationController 和 UITabBarController 就是很好的例子。
他们有不同的逻辑和不同的用处,但是他们有共同的地方,那就是每次只为用户展示一个View Controller。
另外一个关于容器控制器的例子是UISplitViewController,它可以一次展示给用户两个控制器。
简单说,你的自定义容器的本质任务就是管理controller,规定哪个需要显示,哪个不需要,如何让用户在controller之间进行切换。