【iPhoneアプリ開発入門】UIViewControllerとライフサイクル
今回は初心者の方向けにUIViewControllerの話とそのライフサイクルについて説明していきます。
さっそくですがUIViewControllerは大きく二つの役割に分けられます。
目次
Content View Controller
コンテンツを画面に表示するために使われるViewControllerです。
画面を作るための一般的な役割を担います。
Container View Controller
こちらはコンテナとしての役割を担うViewControllerになります。
内包するViewControllerと親子関係を持ち、ほかのViewControllerとのやり取りを調整する役割を持っています。
説明だけだと分かりにくいかもしれませんが、Container View Controllerの代表的な例としては下記のものがあります。
- UINavigationController
- UITabBarController
- UISplitViewController
- UIPageViewController
基本的にこの二つのViewControllerは両方ともUIViewControllerのサブクラスであり構造的な違いはありません。
ですのでUITabBarControllerにUINavigationControllerを内包することも普通にできます。
ViewControllerのライフサイクル
ではここからはViewControllerのライフサイクルについて見ていきます。
ViewControllerの初期化について
まず初期化の方法についてですが二通りやり方があります。
それぞれを見ていきましょう。
ストーリーボードを使ってViewControllerを実装する場合
インスタンスが生成された直後に何らかの処理が必要な場合は、awakeFromNibメソッドをオーバーライドします
ViewControllerをプログラムで初期化する場合
- loadViewメソッドをオーバーライドしてプログラムでビューを生成して設定する
- loadViewメソッドをオーバーライドする場合、superを呼び出してはいけない
- InterfaceBuilderを利用している場合、このメソッドをオーバーライドしてはいけない
viewDidLoad
- InterfaceBuilderを使用している場合、追加のビューの初期化などはここで行う
- このメソッドはInterfaceBuilder、またはloadViewメソッドのどちらを使っていても呼び出される
ViewControllerの表示関連の通知
ここで紹介する4つのメソッドをオーバーライドする場合、このメソッド内のどこかでsuperを呼び出す必要があります
viewWillAppear
- ビュー階層に追加される直前に呼ばれる
viewDidAppear
- ビュー階層に追加された直後に呼ばれる
viewWillDisappear
- ビュー階層からビューが削除される直前に呼ばれる
viewDidDisappear
- ビュー階層からビューが削除されたタイミングで呼ばれる
表示の変化を判定する
下記のメソッドを利用することでビューの表示が変化した理由を判断することもできます。
isMovingFromParentViewController
- ViewControllerのビューが消えようとしているのが、ViewControllerが親であるContainer View Controllerから削除されたためであるかどうかを判定する
- viewWillDisappear:、viewDidDisappear:メソッド内で使用すること
isMovingToParentViewController
- ViewControllerのビューが現れようとしているのが、ViewControllerがContainer View Controllerに子として追加されたためであるかどうかを判定する
- viewWillAppear:、viewDidAppear:メソッド内で使用すること
isBeingPresented
- ViewControllerのビューが現れようとしているのが、別のViewControllerにより当該のViewControllerが表示されたためかどうかを判定する
- viewWillAppear:、viewDidAppear:メソッド内で使用すること
isBeingDismissed
- ViewControllerのビューが消えようとしているのが、ViewControllerが削除されたためかどうかを判定する
- viewWillDisappear:、viewDidDisappear:メソッド内で使用すること
レイアウト関連の通知
viewWillLayoutSubviews
SubViewsのレイアウトを調整する直前に呼ばれる
viewDidLayoutSubviews
SubViewsのレイアウトを調整した直後に呼ばれる
viewWillLayoutSubviewsとviewDidLayoutSubviewsに関する補足
ViewControllerのビューのサイズが変わる際の処理の流れ
- ViewControllerのビューのサイズが変わる
- AutoLayoutが無効の場合、Autosizingに従ってビューのサイズが変わる
- ViewControllerのviewWillLayoutSubviewsメソッドが呼び出される
- ビューのlayoutSubviewsメソッドが呼び出される
- ViewControllerのupdateViewConstraintsメソッドが呼び出される
- UIViewControllerクラスのupdateViewConstraintsメソッドはビューのupdateConstraintsメソッドを呼び出す
- 制約の更新後、新しいレイアウトを計算しビューの位置を調整する
- ViewControllerのviewDidLayoutSubviewsメソッドが呼び出される
AutoLayoutを使っている場合、下記の流れで制約が更新される
その他
viewWillUnloadとviewDidUnload
iOS6からdeprecatedとなり、リソースの解放はここではできなくなりました
Container View Controllerについて
Container View Controllerの使い方について補足しておきます。
これは以前作った資料の内容を一部修正して掲載しています。
Container View Controllerを使うことでできること
- UINavigationController、UITabBarControllerのように独自のtransitionを組み込んでViewControllerを管理する仕組みを実装できます
Container View Controllerの初期化時に各ViewControllerへ依存性の注入を行うなどの処理も挟めます - ViewControllerから別のViewControllerを表示することが簡単に実現できます
例えばログイン状態を確認してログイン画面を出すかその他の画面を出すかの切り替えが容易にできます
ViewDidAppear:などでログインしてなかったらログイン画面のモーダルを出すようなコードは必要なくなります
では具体的な使い方を見ていきます。
コンテナへViewControllerを追加する方法
- addChildViewController:を呼び出すそのタイミングで自動的に追加した子のwillMoveToParentViewController:が呼び出される
- 子のビューを自身のビュー階層に追加する
子のdidMoveToParentViewController:を明示的に呼び出して、処理が終了した旨のシグナルを送信する(transitionが無い場合、すぐに呼び出す)
1 2 3 4 5 |
[sourcecode lang="swift"] addChildViewController(contentViewController) view.addSubview(contentViewController.view) contentViewController.didMoveToParentViewController(self) [/sourcecode] |
コンテナからViewControllerを削除する方法
- 子のwillMoveToParentViewController:を引数にnilを渡して呼び出し削除されることを通知する
- 子のビューをビュー階層から削除する
- 子のremoveFromParentViewControllerを呼び出して、コンテナから削除する
そのタイミングで子のdidMoveToParentViewController:が自動的に呼び出される
1 2 3 4 5 |
[sourcecode lang="swift"] contentViewController.willMoveToParentViewController(nil) contentViewController.view.removeFromSuperview() contentViewController.removeFromParentViewController() [/sourcecode] |
transitionFromViewController:
1 2 3 4 |
[sourcecode lang="swift"] transitionFromViewController:toViewController:duration: options:animations:completion: [/sourcecode] |
このメソッドは自動的に新しいビューを追加し、アニメーション表示を行ってから古いビューを削除します。
そのためaddSubview:、removeFromSuperviewメソッドを手動で呼び出す必要はありません。
例えば下記のように使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[sourcecode lang="swift"] func transitionFromViewController(fromViewController: UIViewController, toViewController: UIViewController) { fromViewController.willMoveToParentViewController(nil) addChildViewController(toViewController) let width = view.bounds.size.width let height = view.bounds.size.height let startFrame = CGRectMake(0, height, width, height) toViewController.view.frame = startFrame let endFrame = CGRectMake(0, 100, width, height) transitionFromViewController(fromViewController, toViewController: toViewController, duration: 0.25, options: .TransitionNone, animations: { toViewController.view.frame = fromViewController.view.frame fromViewController.view.frame = endFrame }) { _ in fromViewController.removeFromParentViewController() toViewController.didMoveToParentViewController(self) } } [/sourcecode] |
shouldAutomaticallyForwardAppearanceMethods
このメソッドをオーバーライドしてNOを返すことでトランジションが発生した際の通知を、子のViewControllerに自動的に送らないようして、手動で行うことができます(デフォルトはYES)
例えば下記メソッドを呼ぶタイミングを手動で調整できます
- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:
注意点として手動で行う場合、上記メソッドを直接呼びだすのではなく、代わりにbeginAppearanceTransition:、endAppearanceTransitionメソッドを呼び出すようにします
子のViewControllerに通知をしたくない、もしくは任意のタイミングで呼びたいケースがあれば使いましょう。
簡単な例を記載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
[sourcecode lang="swift"] override func shouldAutomaticallyForwardAppearanceMethods() -> Bool { return false } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) contentViewController?.beginAppearanceTransition(true, animated: animated) } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) contentViewController?.endAppearanceTransition() } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) contentViewController?.beginAppearanceTransition(false, animated: animated) } override func viewDidDisappear(animated: Bool) { super.viewDidDisappear(animated) contentViewController?.endAppearanceTransition() } [/sourcecode] |
まとめ
UIViewControllerの基本的な話とContainer View Controllerについて説明させていただきました。
UIViewControllerはiOSで画面を作っていく上で、切っても切り離せないものです。
致命的な不具合を発生させないようにするためにも、しっかりと基本を理解した上で使うようにしましょう。