Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

1.4 UIViewController2 ModalViewController (storyboard)

yuichi takeda edited this page Jan 28, 2015 · 8 revisions

参考 : UIViewController Class Reference | View Controller Programming Guide for iOS

概要

UIViewControllerには他のViewControllerと連携して新しいViewControllerを表示するなどの役割もあります。 連携のやり方には代表的なものに Modal, Navigation Controller, TabBarController などの方法があり、この章ではModalを用いた方法を解説します。

Modal View Controllerは「現在のViewControllerで行っている操作を一時中断して新しいViewControllerを表示する」というケースで利用されます。公式ドキュメントには以下のようなケースで使うことを想定しています。

  • ユーザから直ちに情報を収集するため
  • 何らかのコンテンツを一時的に表示するため
  • 作業モードを一時的に変更するため
  • デバイスの向きに応じて代替のインターフェイスを実装するため
  • 特殊なアニメーショントランジションを使用する(またはトランジションなしの)新しいビュー 階層を表示するため

UIViewController は一つの ModalView を表示することが可能で、そのときに、Modal を表示する ViewController と ModalViewController には親子関係ができます。

modal1

View Controller Programming Guide for iOS から引用

具体的には親の ViewController の property:presentedViewController に表示されている ModalViewController の参照が代入され、ModalViewController の propterty:presentingViewController に親の ViewController の参照が代入される。

注意:modalViewController property は iOS6 から deplecated なので使用しないようにしましょう。

modal_deprecated

また、ModalViewController の上に ModalViewController をだすこともできる。

modal2 View Controller Programming Guide for iOS から引用

表示方法

表示方法は storyboard から直接表示する方法と、コード上からViewControllerを生成して表示する方法があります。

storybaordから直接表示する方法

1.3のサンプルプロジェクトをさらに改造します。https://github.com/mixi-inc/iOSTraining/tree/master/SampleProjects/1.3/MyFirstProject

追加で新しい View Controller のサブクラスを作ってください。クラス名は "MySecondViewController" としました。 できたら1.3と同様にstoryboardにViewControllerを追加して、クラスを"MySecondViewController"にしてください。 わかりやすくするために、MySecondViewController上に何かラベルを配置しておくと良いかもしれません。

segueを追加して表示する

segue(セグエ)とは二つのシーン間の遷移方法についての設定のことです。どのViewControllerからどのViewControllerを、どのように表示するか、などを設定します。 主に、storyboard上で遷移を決めるときに利用します。

storyboard上にViewControllerを追加できたら、MixiSampleViewController上のボタンをタップした時に、MySecondViewControllerを表示できるようにsegueを追加しましょう。

storyboardのMixiSampleViewControllerの上のUIButtonをcontrolボタンを押しながらドラッグします。 するとマウスポインタ上とボタンの間に青い線が出ると思います。そのままMySecondViewController上までドラッグします。(図3)

図3 図3

MySecondViewControllerまできたらドロップしてください。すると、図4のようなパネルが出ると思うので、その中から "present modally" を選択します。

図4 図4

ここまでできれば、segueの追加は完了です。 実はここまでできれば、MySecondViewControllerは表示することができます。シミュレータから実行して、MixiSampleViewControllerのボタンをタップするとMySecondViewControllerが表示されると思います。

プログラム上からsegueを実行する

今のサンプルではボタンをタップした時に自動的にsegueが実行され、MySecondViewController が表示されました。 実際にアプリを作る際はデータのロード完了時まで遷移したくない、などのケースもあるため任意のタイミングで実行する必要があります。 そのようなケースではプログラム上からsegueを実行します。

MixiSampleViewControllerにもう一つボタンを追加し、ボタンタップ時に呼ばれるメソッドを一つ追加してください。 今回は secondButtonTapped: というメソッドにしています。

ボタンとハンドラ追加

次にstoryboard上でsegueを選択し、Xcodeの右側にあるユーティリティのAttribute Inspectorから Stroryboard Segue Identifierを入力します。 このIdentifierはstoryboard上で実行されるsegueの識別子でプログラムから呼び出す時などに使います。名前については他とIdentifierと重複しない限り自由につけることができます。 今回は "presentMySecondViewController" としました。

img

それではボタンタップ時に呼ばれるメソッドにこのsegueを呼び出します。呼び出すには UIViewControllerのインスタンスメソッドである -performSegueWithIdentifier:sender: を利用します。 先ほど定義したメソッドの中で -performSegueWithIdentifier:sender: を呼び出します。一つ目の引数はSegueのIdentifierで、先ほどstoryboard上で設定したものを利用します。二つ目のsegueはこのsegueを呼び出したクラスです。よくselfを代入します。

- (IBAction)secondButtonTapped:(id)sender {
    [self performSegueWithIdentifier:@"presentMySecondViewController" sender:self];
}

新たに追加したボタンをタップしたときにMySecondViewControllerが出ればOKです。

segueで表示するViewControllerにデータを与える

segueを用いて画面遷移を行うとき、表示したいViewControllerに何かデータを渡して遷移することがよくあります。 そのようなケースではUIViewControllerのインスタンスメソッド-prepareForSegue:senderを利用します。 UIViewControllerのサブクラスを作った際に自動的にコードスニペットが.mファイルにコメントアウトで記述されています。 このコメントアウトを外して実際に実装します。

この-prepareForSegue:senderはsegueが呼ばれて実際に実行されるまでの間、その遷移に関して追加で行う処理がある場合に実装するメソッドとなります。 引数は実行されるsegueとsenderです。

画面遷移は一つの画面に対して複数あることがあります。そのためどの画面遷移かを見分けて処理を行う必要があります。 その際は segue.identifier を用いて切り分けます。

また、遷移先のViewControllerが必要な場合は segue.destinationViewController で必要なViewControllerを取得することができます。

以下のようなサンプルになります。

#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.

    if ([segue.identifier isEqualToString:@"presentMySecondViewController"]) {
        UIViewController *destination = segue.destinationViewController;
    }

}

segueを使わずにViewControllerを表示する

segueを一切使わず、ViewControllerを表示することもできます。iOS4以前はstoryboardがなくそれに伴ってsegueを利用することもできませんでした。 その際の資料については 1.4-UIViewController2---ModalViewController を見ていただくとよいと思います。

storyboardを使いつつ、ただsegueを利用しない、という方法について簡単に紹介します。 先ほど実装したメソッド -secondButtonTapped: を修正して利用します。

まずはstoryboard内にあるUIViewControllerにIdentifierをつけます。 ユーティリティのIdentity InspectorからStoryboard ID をつけます。このIDをつけることで、コード上から このViewControllerを一つだけ生成することが可能になります。ここにつけるIDは他のものと被らなければ問題ありません。 ここではクラス名と同じで"MySecondViewController"としました。

img

では実際に生成して表示します。#import "MySecondViewController.h" を忘れないでください。

生成するには UIStroyboardのインスタンスメソッド -instantiateViewControllerWithIdentifier:を用います。 このメソッドの引数に先ほど定義したstoryboardIDを渡します。UIViewControllerがstoryboardから生成された場合、プロパティにstoryboardがあるのでそれを利用します。

ViewControllerからViewControllerを表示するにはメソッド presentViewController:animated:completion: を用います。 このメソッドの一つ目の引数に表示したいViewControllerを渡します 。

- (IBAction)secondButtonTapped:(id)sender {
    MySecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"MySecondViewController"];
    [self presentViewController:secondViewController animated:YES completion:nil];
}

このような実装になります。同じく表示されればOKです。

ModalViewControllerを閉じる

ModalViewControllerを表示できたら、次はModalViewControllerを閉じる方法について解説します。 閉じ方はdelegateパターンを用いる方法とUnwind Segue を使う方法の2パターンがあります。

delegateパターンを用いて閉じる

まずは表示しているMySecondViewController上にボタンとボタンタップ時に呼ばれるメソッドを定義してください。 サンプルコードではメソッド名を buttonTapped: としました。 ボタンがタップされた時 buttonTapped: が呼ばれます。このメソッドが呼ばれた時に画面を閉じるコードを書いていきます。

モーダルを閉じるメソッド

表示したModalViewControllerを閉じるにはUIViewControllerのインスタンスメソッド dismissViewControllerAnimated:completion: を用います。

MixiSampleViewControllerか、MySecondViewControllerの内部でこのメソッドを呼ぶことでモーダルを閉じることができます。 どちらのViewControllerからも閉じることができるのですが、MixiSampleViewControllerに閉じる責務があります。

モーダルでViewControllerを表示する時、表示するViewControllerと表示されるViewControllerの関係が存在します。 今回のケースでは MixiSampleViewControllerが表示する側、MySecondViewControllerが表示される側となります。 またプロパティとして、表示している側は presentingViewController、表示されている側は presentedViewControllerとなります。

そして、表示している 側のViewControllerに表示したViewControllerを閉じる責務が発生します。 あるクラスのインスタンスを生成したら、生成したクラスが責任を持って処理を行う、という原則に則っています。

ただ、実際にモーダルを表示して何か操作を行った時、使った側がその操作の完了を検知できないと処理に不都合がある場合があります。 そのために表示した側が閉じる処理を行う、というルールになっています。

img

delegateを使わないとすると

さて、このように表示されたMySecondViewControllerを閉じるのは表示したMixiSampleViewControllerになります。 MySecondViewControllerのボタンがタップされた時に、どうすれば MixiSampleViewController が閉じるアクションを行えるのでしょうか。 MySecondViewControllerからMixiSampleViewControllerにボタンがタップされた、あるいは作業が完了したことを通知する必要があります。

一番簡単な方法は、MySecondViewControllerがプロパティとしてMixiSampleViewControllerのインスタンスへのポインタを持っておき、 ボタンがタップされた時にそのインスタンスメソッドを呼ぶ方法です。

@interface MySecondViewController : UIViewController
// MySecondViewControllerがMixiSampleViewControllerのプロパティを持つ
@property (nonatomic, strong) MixiSampleViewController *sampleViewController;
@end
MySecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"MySecondViewController"];
// MySecondViewControllerのMixiSampleViewControllerプロパティにselfをセット
secondViewController.sampleViewController = self;
[self presentViewController:secondViewController animated:YES completion:nil];
// MySecondViewControllerのタップハンドラ
- (IBAction)buttonTapped:(id)sender {
    // sampleViewControllerのメソッドを読んでいる
    [self.sampleViewController dismissViewControllerAnimated:YES completion:nil];
}

この方法で、ViewControllerを閉じることができますが、

  • 結局MySecondViewControllerが自分でモーダルを閉じている
  • MySecondViewControllerがMixiSampleViewController に依存している

という問題点があります。

Objective-Cにおけるdelegateパターン

そこでこのようなケースでは、delegateパターンを用いて解決します。 Delegateパターンを端的に言うと、メソッドのインタフェースだけ宣言しておき、あるクラスはそのメソッドを実装します。このメソッドを利用する側は、そのクラスについて知る必要はなく、インタフェースに従うのみとすることでクラス間の依存を取り除くデザインパターンです。

今回のモーダルを閉じるケースをサンプルにDelegateパターンを実装します。 Delegateパターンを用いる際、Objective-Cでは プロトコル という仕組みを利用します。 プロトコルは @protocol ~ @end の部分でメソッドを宣言します。またこのプロトコルのメソッドを実装することを プロトコルに準拠する と呼ぶことが多いのですが、メソッドを実装してプロトコルを準拠します。

// MySecondViewController.h 内
@protocol MySecondViewControllerDelegate <NSObject>
- (void)secondViewControllerButtonTapped;
@end

このケースでは、 MySecondViewControllerDelegate がプロトコル名で - (void)secondViewControllerButtonTapped; がこのプロトコルに準拠したクラスが実装するメソッドになります。

次に、MixiSampleViewControllerがプロトコル MySecondViewControllerDelegate を準拠させます。 クラスの宣言の際に @interface クラス名 : スーパークラス名 <準拠するプロトコル名> とすることで、このクラスはこのプロトコルに準拠しています、という宣言になります。

@interface MixiSampleViewController : UIViewController <MySecondViewControllerDelegate>
// 中略
@end

次に実装側でプロトコルのメソッドを実装します。

@implementation MixiSampleViewController
// 中略
- (void)secondViewControllerButtonTapped
{
    // 後で実装します。
}
// 中略
@end

こうすることでプロトコルを採用したDelegateパターンを使うことができます。

実際にdelegateパターンを利用する

さて、実際にこのパターンを用いてモーダルを閉じます。 まず、MySecondViewControllerにプロパティを一つ追加します。

@interface MySecondViewController : UIViewController
@property (nonatomic, weak) id<MySecondViewControllerDelegate> delegate; // <- 追加
@end

ポイントとしては

  • プロパティのタイプが weak になっている点。これは循環参照を避けるためにあります。
  • id<MySecondViewControllerDelegate> という型。これは MySecondViewControllerDelegateに準拠したid型となります。つまりMySecondViewControllerDelegateに準拠していたらどんなクラスでもOKということです。

次にMySecondViewControllerのボタンタップハンドラを次のように修正します。

- (IBAction)buttonTapped:(id)sender {
    [self.delegate secondViewControllerButtonTapped];
}

MySecondViewControllerのdelegateプロパティのメソッドである secondViewControllerButtonTapped を呼び出しています。 delegateプロパティはどんなクラスかは分かりませんが、MySecondViewControllerDelegateに準拠しているのでこのメソッドがあるはずなので呼び出すことができます。 (はず、としたのはコンパイル時にこのメソッドが定義されていなくてもコンパイルエラーにならず、実行時にランタイムエラーとなるからです。)

次はMixiSampleViewControllerを修正します。MySecondViewControllerを表示するときのメソッド -secondButtonTapped:-secondViewControllerButtonTapped を合わせて以下のようにします。

- (IBAction)secondButtonTapped:(id)sender {
    MySecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"MySecondViewController"];
    secondViewController.delegate = self;
    [self presentViewController:secondViewController animated:YES completion:nil];
}

- (void)secondViewControllerButtonTapped
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

これで正しくモーダルを閉じることができるようになります。表示してからモーダルを閉じるまでの一連の流れを見てみましょう。

  1. MixiSampleViewControllerのsecondButtonTappedが呼ばれる
  • MySecondViewControllerインスタンスを生成し、delegateプロパティに self (MixiSampleViewControllerインスタンス) を代入する
  • [self presentViewController:secondViewController ... でMySecondViewControllerを表示する
  • MySecondViewControllerのボタンをタップすると、タップハンドラ (-buttonTapped) が呼ばれる
  • MySecondViewControllerの self.delegate すなわち 2.でセットした MixiSampleViewControllerインスタンス のメソッド -secondViewControllerButtonTapped が呼ばれる
  • MixiSampleViewControllerのsecondViewControllerButtonTapped内で [self dismissViewControllerAnimated... を呼ぶことでモーダルが閉じる

以上を実装してみて、正しくモーダルが閉じればOKです!

Unwind Segue

Unwind Segueとはstoryboardで遷移を行った元のView Controllerまで戻るSegueになります。例えばモーダルを表示した、Navigation Controllerでpushしたなどの遷移を元に戻すことができます。設定はstoryboardベースになります。 Unwind Segueのリファレンスについてはこちらをご覧ください。 Technical Note TN2298: Using Unwind Segues

先ほどのケースではdelegateを用いてModalを閉じましたが、ここではUnwind Segueを用いてモーダルを閉じるサンプルになります。 MySecondViewController上に新しくボタンを追加し、そのボタンをタップしたら表示したモーダルが閉じる、というサンプルになります。

Unwind Segue実行時のメソッドを定義

まずはUnwind Segueが実行されたときのメソッドを定義します。メソッドの定義を行うのは、遷移が戻った後に表示されるViewController上になります。 今回のケースでは モーダルが閉じた後に表示されるViewControllerは MixiSampleViewController になるので、MixiSampleViewControllerにメソッドの定義を行います。モーダルとして表示されている MySecondViewController 上ではないので注意してください。

定義するメソッドには以下の制約を満たさなければなりません。

  • 戻り値の型は IBAction
  • 引数は一つだけで、その型は UIStoryboardSegue への参照 (i.e. UIStoryboardSegue *型)

これはstoryboardとこのメソッドを紐づけるための制約になります。メソッド名には特に制約はありません。 例えば以下のようにメソッドを定義してください。

- (IBAction)unwindToSampleViewController:(UIStoryboardSegue *)segue
{
    // segue実行時にここが実行される
}

ボタンを追加し、Segueを紐づける

次はstoryboard上での操作になります。まずSegueをトリガーするためのボタンを配置します。MySecondViewControllerが表示されている時に行うアクションなのでMySecondViewController上にボタンを一つ配置してください。

次にMixiSampleViewController上部にアイコンが "Mixi Sample View Controller", "First Responder", "Exit" などが並んでいると思うのですが、この中からExitを右クリックするとパネルが表示されます。 ここに表示されているSegueがMixiSampleViewControllerに巻き戻ってくることのできるSegueとなります。おそらくここに先ほど定義したメソッドが表示されていると思います。このメソッドをドラッグアンドドロップで今追加したボタンと接続します。

img

このように接続を行うことができれば、作業は完了です。Runボタンから実行し、今追加したボタンをタップするとモーダルが閉じると思います。 無事閉じれば完了です。

演習課題

ここまででモーダルを出現、閉じるという一連の画面遷移について学びました。この章での演習課題は、 この一連の画面遷移を再現することになります。

1.3章のプロジェクトの続きから行います。

演習1.

新しいViewControllerのサブクラスを追加してstoryboardに追加してください。 次に、ボタンをタップしたときにこのViewControllerがモーダルで表示されるようなsegueをstoryboardのみで作ってください。

演習2.

演習1.に引き続き、新しいボタンを追加し、ボタンがタップされた時に同じsegueが実行されるようにしてください。ただし演習1.とは異なり、コード上からsegueを実行してください。(i.e. performSegueWithIdentifier:sender:を使う)

演習3.

さらに引き続き、新しいボタンを追加し、ボタンがタップされたときに新しいViewControllerをsegueを使わず、 presentViewController:animated:completion: を用いてモーダルが表示されるようにしてください。

演習4.

新しいViewControllerにボタンを追加し、ボタンをタップしたら表示されたViewControllerが閉じるようにしてください。 ただし新しいViewController自身が閉じるのではなくdelegateを使うようにしてください。 またどのボタンをタップしても閉じるようにしてください。

※ヒント segue経由で画面を表示するときのdelegateの設定は -prepareForSegue: sender: の中で行います。

回答

回答については iOSTraining/SampleProjects/1.4/MyFirstProject-answer をごらんください。

はじめに

  1. iOSについて

  2. Xcode最初のステッフ

  3. 導入

  4. Objective C の基礎

  5. メモリ管理

  6. 1.3 UIViewController1 UIViewController のカスタマイズ(xib, autoresizing)

  7. 1.3 UIViewController1 UIViewController のカスタマイズ(storyboard)

  8. UIViewController2 - ModalViewController

  9. UIViewController2 - ModalViewController(storyboard)

  10. UIViewController3 - ライフサイクル

  11. HomeWork 1 Objective C の基本文法

  12. HomeWork 2 UIViewControllerとModalViewController

  13. HomeWork 3 UIViewController + Animation

  14. UIKit 1 - container, rotate-

  15. UINavigationController

  16. UITabController

  17. Custom Container View Controller

  18. Supporting Multiple Interface Orientations

  19. HomeWork 1 - タブバーからモーダルビューを表示する

  20. HomeWork 2 - NavigationController

  21. HomeWork 2.3 デバイスことに回転対応

  22. UIKit 2- UIView -

  23. UIView

  24. UIView のカスタマイズ

  25. UIView Animation

  26. HomeWork 1 - UIScrollView

  27. UIKit 3 - table view -

  28. UITableView について

  29. UITableViewとNavigationController

  30. custom UITableViewCell の作成

  31. UITableViewのその他のオプション、カスタマイズ

  32. HomeWork 1 - Dynamic height with a custom uitableviewcell

  33. UIKit 4 - image and text -

  34. UIImagePickerController

  35. Assets Library

  36. UITextFiled, UITextView

  37. KeyboardNotification

  38. Homework 1 - フォトの複数枚選択

  39. ネットワーク処理

  40. NSURLConnection

  41. JSONのシリアライズとデシリアライズ

  42. UIWebView

  43. ローカルキャッシュと通知

  44. NSUserDefaults, Settings Bundle

  45. NSFileManager

  46. Key Value Observing

  47. NSNotification、NSNotificationCenter を用いた通知

  48. UILocalNotification

  49. Blocks, GCD

  50. Blocks

  51. GCD

  52. 【演習】GCD,-Blocksを用いたHTTPリクエストマネージャの作成

  53. 設計とデザインパターン

  54. クラス設計 1

  55. クラス設計 2

  56. [クラス設計演習] (https://github.com/mixi-inc/iOSTraining/wiki/9.3-%E3%82%AF%E3%83%A9%E3%82%B9%E8%A8%AD%E8%A8%88%E6%BC%94%E7%BF%92)

  57. 開発ツール

  58. Instruments, デバッガ

  59. CocoaPods

  60. テスト

  61. iOS開発におけるテスト

  62. GHUnit

  63. Kiwi

  64. KIF

  65. In-App Purchase

  66. In-App Purchase

  67. 付録

  68. Tips of Xcode

  69. Auto Layout 入門

  70. Auto Layout ドリル

Edit sidebar

Clone this wiki locally