はじめに
iOS5からUIViewControllerを自前のクラスにaddChildViewController:メソッドで追加できるようになった。
最近では、こういった自前コンテナは主にFacebookやPath2.0のスライドメニュー風のUI開発なんかに使われていると思う。
このaddChildViewController:メソッドの後には必ずdidMoveToParentViewController:メソッドを呼ぶという説明があるが、これの意味がよくわからなかったのでそのメモ。
例えば
//自前コンテナ(self)にchildViewControllerを追加
[self addChildViewController:childViewController];
//その後didMoveToParentViewControllerを実行しなければいけない
[childViewController didMoveToParentViewController:self];
この追加(add)したあとになぜ完了(didMove)を明示的に知らせないといけないのか謎という感覚。また、これがなくてもこちらの想定通り動くのでずっと疑問だった。
結論としては
自前コンテナでaddChildViewController:を実行した後は、やはり必ずdidMoveToParentViewController:を呼ぶ。これはUIViewController自身が他のコンテナに追加(もしくは削除)されている最中かどうかを知ることで処理を分岐する事もできるようになっていて、それを判断するために完了したことを通知する必要がある。
ちなみに、正しくコードをかけていない場合、追加したViewControllerのviewWillAppear:が動作しない場合がある。
これはどのような事かというと、コンテナのviewを表示したいだけならばViewControllerのviewに対して子ViewControllerのviewをaddSubViewすればいいだけだと思ってしまう。しかしそのViewが表示されたかどうかという判断にはならないためaddChildViewControllerがそもそも必要になってくる。
説明
UIViewControllerの追加サイクル
そもそもUIViewControllerの追加には「開始」と「終了」がかならずセットで必要となる。
・追加: addChildViewController:
・開始: willMoveToParentViewController:(add後に自動で呼ばれる)
・完了: didMoveToParentViewController:(任意のタイミングで呼ぶ)
まず自前コンテナへの追加のためにaddChildViewController:した際に、ViewControllerの追加開始の合図であるwillMoveToParentViewControler:は自動で呼ばれる。
開始は自動で呼ばれるが、それが終了したことをコールするためのdidMoveToParentViewController:メソッドは自動で呼ばれないため必要となる。
終了をコールしないと何に困るのか
もともとUIViewControllerは自身が他のコンテナに追加(もしくは削除)されている最中かどうかを知ることで処理を分岐する事もできるようになっていて、これを判断するのに完了を呼ぶ必要がある。
ちなみに、追加されている途中かどうかはUIViewControllerのisMovingToParentViewControllerメソッドによって取得できる。
didMoveToParentViewController:がなぜ自動で呼ばれないようになっているか
では、なぜdidMoveToParentViewController:が自動で呼ばれないかというと、画面遷移にトランジションを指定したい場合が想定されているからだと思う。
例えば次のコードのように、完了までに時間がかかる(実装者が時間を指定できる)場合に、完了のメソッドが明示的にコールできるようになっていることでその意味が出てくる。
/*fromViewControllerはすでにコンテナに追加されているとする*/
//toViewControllerをコンテナに追加する
[self addChildViewController:toViewController];
//fromからtoへ画面遷移のトランジションを行う
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:1.0
options:UIViewAnimationOptionTransitionNone
animations:^{
// ここで何かしらの遷移アニメーション
}
completion:^(BOOL finished) {
// 追加完了を明示的に行う
[toViewController didMoveToParentViewController:self];
}];
まとめ
上記のように、画面遷移のトランジションを使わなくてもaddChildViewController:したらdidMoveToParentViewController:を呼び出すことを忘れないようにしたらよいでしょう。
もし、突っ込みどころ等あればコメントなり編集リクエストがあれば嬉しいです。
参考
カスタムContainer View Controllerを作る(ルールが分かりやすく纏められている)
http://qiita.com/edo_m18/items/8b6b457f82b185ab1f6a
Cocoa練習帳: [iOS]独自のコンテナViewController
http://www.bitz.co.jp/weblog/?date=20120923
isMovingToParentViewControllerメソッドについてはiOS View Controllerカタログの「ナビゲーションスタックの変化の監視」
https://developer.apple.com/jp/devcenter/ios/library/documentation/ViewControllerCatalog.pdf