UIViewControllerまとめ


Appleの[iOS View Controllerプログラミングガイド](https://developer.apple.com/jp/devcenter/ios/library/documentation/ViewControllerPGforiOS.pdf)を読んでいます。
そこで書いていることを把握した範囲で書いていきたいと思います。
まず、役割。

##UIViewControllerの役割

いわゆるMVCアーキテクチャのCを担当すると書かれています。
そもそも名前が「Controller」ですもんね。
そしてiOSアプリの中では中心的存在になります。

###UIViewControllerはリソースを管理する

自身のviewプロパティを親として、（必要であれば）subviewを管理します。
また同時に、その画面で必要なリソース（例えばMVCのModel）も管理します。
つまり、「今表示している画面」についてほぼすべての責任を負っているとも言えます。
そして他のいくつかのViewControllerと連携しながらアプリケーションを構築していきます。

###他のViewControllerと連携する

ViewControllerは他のViewControllerと連携します。
並列な関係もあれば、含有される関係もあります。
親子関係を作る一般的な例は「NavigationController」でしょう。
そして並列な関係は「TabViewController」が分かりやすいです。

####連携にはデリゲートやプロパティを使う

基本的に、あるViewControllerは他のViewControllerから生成され、追加されます。
その際に生成したViewControllerのプロパティに必要な値を設定したり、あるいは生成されたViewController側で発生したイベントをデリゲートによって、生成した側のViewControllerに処理を委譲したりして処理を進めていくことになります。

###メモリ管理も担う

リソースを管理する、ということはメモリ管理も担っていることになります。
ViewControllerは、メモリ不足が生じるとアプリケーションから`didReceiveMemoryWarning:`が呼ばれます。
ここで、可能であればビューの解放などを行い、メモリ不足が解消できるよう処理をします。

##UIViewControllerのライフサイクル

UIViewControllerはライフサイクルを持っています。
初期化による生成から始まり、必要なくなった際の解放までがひとつのサイクルです。
その中でフレームワークによってメモリを効率的に活用できるよう、適切にライフサイクルに関するメソッドが呼ばれ、処理されていきます。

###表示に関するライフサイクル

最初勘違いしていたんですが、初期化をしただけではまだビュー郡は生成されていません。
これはメモリ管理の上でも重要です。
ではいつ生成されるのか。それは、自身の`view`プロパティにアクセスがあったとき、とされています。
（ただ、おそらく表示時に`view`プロパティを明示的にsubviewに追加する、みたいなことはせず、ViewControllerの生成・表示フローを行うことで適切に呼ばれると思われます）

####ビュー関連のライフサイクル

1. loadView
2. viewDidLoad

ビュー関連は以下のふたつの間で生成されます。
よくよく考えれば、メソッド名そのままですね。`loadView`でviewをロードします。
基本的な処理は空の`UIView`を生成し、自身の`view`プロパティに設定します。

もしカスタムビューを設定したい場合はこれをオーバーライドし、ビュー階層を自前で設定した上で、親となるビューを`view`プロパティに設定します。

※追記
コメントで指摘もらっていますが、ビュー郡を追加する場合は`viewDidLoad`のタイミングがいいです。
ここでの説明はあくまで`loadView`が本来やってくれる処理を理解するためのものですので注意してください。

#####サンプルコード

```objc
- (void)loadView
{
	UIView *rootView = [[UIView alloc] initWithFrame:<anySize>];
	UIView *otherView = [[UIView alloc] initWithFrame:<otherSize>];
	[rootView addSubview:otherView];

	self.view = rootView;
}
```

loadViewメソッドが呼ばれ、適切にビュー階層が設定されたのち、`viewDidLoad`メソッドが呼ばれます。


####表示・非表示

上記が、ビューの生成に関するメソッドです。
表示・非表示に関してはこれとは別にサイクルがあり、最初の生成時以外にも、例えば表示していたモーダルビューが消えるタイミングなどでも表示用のメソッドが呼ばれます。

1. viewWillAppear
2. viewDidAppear
3. viewWillDisappear
4. viewDidDisappear

ちなみに、「どのように」表示（あるいは非表示）になるかを知るメソッドも用意されています。

| メソッド名 | 意味 |
|----------|------|
| isMovingFromParentViewController | viewWillDisappear:、viewDidDisappear:メソッド内から呼び出して、消えようとしている理由が親のViewControllerから削除されたかどうかをBOOLで返します。 |
| isMovingToParentViewController | viewWillAppear:、viewDidAppear:メソッド内から呼び出して、表示されようとしている理由が親ViewControllerに子として追加されたかどうかをBOOLで返します。 |
| isBeingPresented | viewWillAppear:、viewDidAppear:メソッド内から呼び出して、表示されようとしている理由が別のViewControllerから表示されたかどうかをBOOLで返します。 |
| isBeingDismissed | viewWillDisapper:、viewDidDisapper:メソッド内で呼び出して、消えようとしている理由が削除されたためであるかをBOOLで返します。 |


##ビューのレイアウト処理

ViewControllerは、レイアウトが変化した際にアプリケーションから通知されます。
通知された際の処理の流れは以下になります。

1. ViewControllerのビューが新しいサイズに変わる
2. auto layout無効であれば、auto resizing maskに従ってビューの大きさや位置が変更される
3. ViewControllerの`viewWillLayoutSubviews`メソッドが呼ばれる
4. ビューの`layoutSubviews`メソッドが呼ばれます。もしビュー階層を構築するためにauto layoutが使われている場合は、以下の手順でレイアウト制約を更新できる
	1. ViewControllerの`updateViewConstraints`メソッドが呼び出される
	2. `updateViewConstraints`メソッド内で、ビュー階層のビューの`updateConstraints`メソッドを呼び出す
	3. レイアウト制約の更新後、新レイアウトを計算しビューの位置を調整する
5. ViewControllerの`viewDidLayoutSubviews`メソッドが呼ばれる

###理想型

Appleのプログラミングガイドには、以下のように記載されています。

>
ビュー自身が位置調整に必要な処理をすべて実行し、ViewControllerはまったく関与しなくてよいのが理想です

できるだけこうなるよう設計するのが望ましいということでしょう。

##ViewControllerの表示とトランジションスタイルの選択

ViewControllerを連鎖して画面を表示していく際、表示時のトランジションスタイルを設定することで、アニメーションに変化をつけることができます。

Appleのプログラミングガイドから引用すると以下の設定があります。

| トランジションスタイル | 使用法 |
|--------------------|-------|
| UIModalTransitionStyleCoverVertical| このスタイルは、ユーザから情報を収集するために現在のワークフ ローに割り込みを行う場合に使用します。ユーザが変更を加える可 能性のあるコンテンツの表示にも使用できます。<br />このトランジションスタイルでは、Content View Controllerが、その View Controllerを明示的に閉じるためのボタンを提供しなければなりません。通常、これは「完了(Done)」ボタンとオプションの「キャンセル(Cancel)」ボタンになります。<br />トランジションスタイルを明示的に設定しない場合は、デフォルトでこのスタイルが使われます。 |
| UIModalTransitionStyleFlipHorizontal | このスタイルは、アプリケーションの作業モードを一時的に変更するために使用します。このスタイルがもっともよく使われるのは、「株価(Stocks)」アプリケーションや「天気( Weather)」アプリケーションなどで、頻繁に変更される可能性がある設定を表示する場合です。これらの設定はアプリケーション全体に影響を与えるものもあれば、現在の画面に特有のものもあります。<br />このトランジションスタイルでは、一般に、アプリケーションの通 常の動作モードに戻るための何らかのボタンを提供します。 |
| UIModalTransitionStyleCrossDissolve | このスタイルは、デバイスの向きが変化したときに代替のインターフェイスを表示するために使用します。このような場合に、向きの 変化の通知に応答して代替のインターフェイスを表示したり閉じたりするのはアプリケーションの仕事です。<br />メディアベースのアプリケーションでは、このスタイルを使用して メディアコンテンツを表示する画面をフェードインすることもできます。 |


##カスタムContainer View Controllerを作る

NavigationViewControllerみたいに、子ViewControllerを伴ってコンテナとして振る舞うViewControllerを自作することもできます。
これについては面白そうなので、別記事にまとめようと思います。
（Gmailアプリなどでちょっと特殊な遷移を実装しているのは、これの仕組みを使っているのかな？）