カスタムContainer View Controllerを作る


Appleの[iOS View Controllerプログラミングガイド](https://developer.apple.com/jp/devcenter/ios/library/documentation/ViewControllerPGforiOS.pdf)は色々と参考になるものが多かったです。
まだ読んでない人はぜひ一度読んでみてください。設計する上でもとても有用な情報が載っています。


##Container View Controller（コンテナ）

Container View Controllerは、コンテナとしての役割を担うViewControllerです。
すでにあるものとしては`NavigationViewController`や`TabViewController`がそれに当たります。
つまり、子となるViewControllerを内包するちょっと特殊なViewControllerです。


###コンテナViewControllerの役割

コンテナというくらいなので、なにかしらのViewController群をまとめる役割を持ちます。
`NavigationViewController`であればスタックとしてViewControllerを管理し、`TabViewController`であれば並列関係のViewContrllerを管理します。

そして必要であれば、コンテナViewController独自のビュー領域（ナビゲーションバーやタブバーみたいなもの）を用意して、それを利用して内包しているViewControllerを表示したり、ということもできます。

ただ、実装を見ると分かりますが、基本的には`UIViewController`のサブクラスでしかないので、コンテナの中にコンテナが、という入れ子構造ももちろん実現することができます。

###コンテナViewControllerの作成手順

コンテナ用のViewControllerを作る際は、通常のViewControllerとは少し考慮することが変わります。
手順としては、

1. `addChildViewController:`メソッドでViewControllerの親子関係を作る
2. ViewControllerの`view`を自身の`view`のsubviewに追加する
3. ViewControllerの`didMoveToParentViewController:`メソッドを、自身を引数にして呼ぶ

という流れです。

(2)のview追加に関しては、通常のUIViewなどを管理するのと同じです。
(1)と(3)が特殊な部分ですが、よく考えればむずかしくありません。

ViewControllerを内包するということは親子関係を作るということ。
そのためのメソッドが`addChildViewController:`メソッドです。
（逆に削除するには`removeFromParentViewController`メソッドを子ViewController側で呼びます）
このあたりは`view`に対する`addSubview`（`removeFromSuperview`）などと同じ理屈ですね。

ちなみに`addChildViewController:`を呼び出すと、自動的に、引数に渡されたViewControllerの`willMoveToParentViewController:`メソッドが呼び出され、これからコンテナViewControllerの子要素になることが通知されるようになっています。

逆に、`removeFromParentViewController`を呼ぶと、`didMoveToParentViewController:`が呼ばれます。

####ライフサイクルはどうなる？

上記のメソッドを適切に呼び出すことで、普段使っているライフサイクル、`viewDidLoad`や`viewWillAppear`などのメソッドが順次呼び出されていきます。


###サンプルコード

Appleのプログラミングガイドのサンプルコードをコメントをつけつつ掲載します。
（サンプルはすべて、コンテナViewControllerのメソッドです）

####子のViewControllerを追加する

```objc
- (void)displayContentController:(UIViewController *)content
{
	// 自身のビューコントローラ階層に追加
	// 自動的に子ViewControllerの`willMoveToParentViewController:`メソッドが呼ばれる
	[self addChildViewController:content];

	// 子ViewControllerの`view`を自身の`view`階層に追加
	[self.view addSubview:content.view];

	// 子ViewControllerに追加が終わったことを通知する
	[content didMoveToParentViewController:self];
}
```

####子のViewControllerを削除する

```objc
- (void)hideContentController:(UIViewController *)content
{
	// これから取り除かれようとしていることを通知する（引数が`nil`なことに注意）
	[content willMoveToParentViewController:nil];

	// 子ViewControllerの`view`を取り除く
	[content.view removeFromSuperview];

	// 子ViewControllerを取り除く
	// 自動的に`didMoveToParentViewController:`が呼ばれる
	[content removeFromParentViewController];
}
```


####ViewControllerの入れ替わりをアニメーションさせる

`NavigationViewController`などのように、ViewControllerの入れ替え時にアニメーションさせることもできます。

```objc
- (void)cycleFromViewController:(UIViewController *)oldC toViewController:(UIViewController *)newC
{
	// 古いViewControllerに取り除かれようとしていることを通知する
	[oldC willMoveToParentViewController:nil];

	CGFloat width  = self.view.bounds.size.width;
	CGFloat height = self.view.bounds.size.height;

	// アニメーションスタート位置を画面下部にする
	CGRect startFrame = CGRectMake(0, height, width, height);
	[self addChildViewController:newC];

	newC.view.frame = startFrame;
	CGRect endFrame = CGRectMake(0, 100, width, height);

	[self transitionFromViewController:oldC
						toViewController:newC
					 			 duration:0.25
								  options:0
						  	   animations:^{
									newC.view.frame = oldC.view.frame;
									oldC.view.frame = endFrame;
							   }
							completion:^(BOOL finished) {
								[oldC removeFromParentViewController];
								[newC didMoveToParentViewController:self];
							}];
}
```


`transitionFromViewController:toViewController:duration:options:animations:completion:`メソッドを使うことによって入れ替わりのアニメーションを実装することができます。

`animations:`にblocksを渡すことで、どういうアニメーションをさせるか、ということができます。
これを使えば、ちょっと変わったアニメーションをもったコンテナViewControllerが作れそうですね。

[今回のサンプルはgithub](https://github.com/edom18/CustomContainerViewControllerSample/tree/master/CustomViewController)にあげてみました。プログラミングガイドをほぼそのまま使ったものですが。