-
Notifications
You must be signed in to change notification settings - Fork 336
1.4 UIViewController2 ModalViewController (storyboard)
参考 : 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 には親子関係ができます。
具体的には親の ViewController の property:presentedViewController に表示されている ModalViewController の参照が代入され、ModalViewController の propterty:presentingViewController に親の ViewController の参照が代入される。
注意:modalViewController property は iOS6 から deplecated なので使用しないようにしましょう。
また、ModalViewController の上に ModalViewController をだすこともできる。
表示方法は storyboard から直接表示する方法と、コード上からViewControllerを生成して表示する方法があります。
1.3のサンプルプロジェクトをさらに改造します。https://github.com/mixi-inc/iOSTraining/tree/master/SampleProjects/1.3/MyFirstProject
追加で新しい View Controller のサブクラスを作ってください。クラス名は "MySecondViewController" としました。 できたら1.3と同様にstoryboardにViewControllerを追加して、クラスを"MySecondViewController"にしてください。 わかりやすくするために、MySecondViewController上に何かラベルを配置しておくと良いかもしれません。
segue(セグエ)とは二つのシーン間の遷移方法についての設定のことです。どのViewControllerからどのViewControllerを、どのように表示するか、などを設定します。 主に、storyboard上で遷移を決めるときに利用します。
storyboard上にViewControllerを追加できたら、MixiSampleViewController上のボタンをタップした時に、MySecondViewControllerを表示できるようにsegueを追加しましょう。
storyboardのMixiSampleViewControllerの上のUIButtonをcontrolボタンを押しながらドラッグします。 するとマウスポインタ上とボタンの間に青い線が出ると思います。そのままMySecondViewController上までドラッグします。(図3)
図3
MySecondViewControllerまできたらドロップしてください。すると、図4のようなパネルが出ると思うので、その中から "present modally" を選択します。
図4
ここまでできれば、segueの追加は完了です。 実はここまでできれば、MySecondViewControllerは表示することができます。シミュレータから実行して、MixiSampleViewControllerのボタンをタップするとMySecondViewControllerが表示されると思います。
今のサンプルではボタンをタップした時に自動的にsegueが実行され、MySecondViewController が表示されました。 実際にアプリを作る際はデータのロード完了時まで遷移したくない、などのケースもあるため任意のタイミングで実行する必要があります。 そのようなケースではプログラム上からsegueを実行します。
MixiSampleViewControllerにもう一つボタンを追加し、ボタンタップ時に呼ばれるメソッドを一つ追加してください。
今回は secondButtonTapped:
というメソッドにしています。
次にstoryboard上でsegueを選択し、Xcodeの右側にあるユーティリティのAttribute Inspectorから Stroryboard Segue Identifierを入力します。 このIdentifierはstoryboard上で実行されるsegueの識別子でプログラムから呼び出す時などに使います。名前については他とIdentifierと重複しない限り自由につけることができます。 今回は "presentMySecondViewController" としました。
それではボタンタップ時に呼ばれるメソッドにこの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に何かデータを渡して遷移することがよくあります。
そのようなケースでは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を表示することもできます。iOS4以前はstoryboardがなくそれに伴ってsegueを利用することもできませんでした。 その際の資料については 1.4-UIViewController2---ModalViewController を見ていただくとよいと思います。
storyboardを使いつつ、ただsegueを利用しない、という方法について簡単に紹介します。
先ほど実装したメソッド -secondButtonTapped:
を修正して利用します。
まずはstoryboard内にあるUIViewControllerにIdentifierをつけます。 ユーティリティのIdentity InspectorからStoryboard ID をつけます。このIDをつけることで、コード上から このViewControllerを一つだけ生成することが可能になります。ここにつけるIDは他のものと被らなければ問題ありません。 ここではクラス名と同じで"MySecondViewController"としました。
では実際に生成して表示します。#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を閉じる方法について解説します。 閉じ方はdelegateパターンを用いる方法とUnwind Segue を使う方法の2パターンがあります。
まずは表示しているMySecondViewController上にボタンとボタンタップ時に呼ばれるメソッドを定義してください。
サンプルコードではメソッド名を buttonTapped:
としました。
ボタンがタップされた時 buttonTapped:
が呼ばれます。このメソッドが呼ばれた時に画面を閉じるコードを書いていきます。
表示したModalViewControllerを閉じるにはUIViewControllerのインスタンスメソッド dismissViewControllerAnimated:completion: を用います。
MixiSampleViewControllerか、MySecondViewControllerの内部でこのメソッドを呼ぶことでモーダルを閉じることができます。 どちらのViewControllerからも閉じることができるのですが、MixiSampleViewControllerに閉じる責務があります。
モーダルでViewControllerを表示する時、表示するViewControllerと表示されるViewControllerの関係が存在します。 今回のケースでは MixiSampleViewControllerが表示する側、MySecondViewControllerが表示される側となります。 またプロパティとして、表示している側は presentingViewController、表示されている側は presentedViewControllerとなります。
そして、表示している 側のViewControllerに表示したViewControllerを閉じる責務が発生します。 あるクラスのインスタンスを生成したら、生成したクラスが責任を持って処理を行う、という原則に則っています。
ただ、実際にモーダルを表示して何か操作を行った時、使った側がその操作の完了を検知できないと処理に不都合がある場合があります。 そのために表示した側が閉じる処理を行う、というルールになっています。
さて、このように表示された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 に依存している
という問題点があります。
そこでこのようなケースでは、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パターンを使うことができます。
さて、実際にこのパターンを用いてモーダルを閉じます。 まず、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];
}
これで正しくモーダルを閉じることができるようになります。表示してからモーダルを閉じるまでの一連の流れを見てみましょう。
- 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とはstoryboardで遷移を行った元のView Controllerまで戻るSegueになります。例えばモーダルを表示した、Navigation Controllerでpushしたなどの遷移を元に戻すことができます。設定はstoryboardベースになります。 Unwind Segueのリファレンスについてはこちらをご覧ください。 Technical Note TN2298: Using Unwind Segues
先ほどのケースではdelegateを用いてModalを閉じましたが、ここではUnwind Segueを用いてモーダルを閉じるサンプルになります。 MySecondViewController上に新しくボタンを追加し、そのボタンをタップしたら表示したモーダルが閉じる、というサンプルになります。
まずはUnwind Segueが実行されたときのメソッドを定義します。メソッドの定義を行うのは、遷移が戻った後に表示されるViewController上になります。 今回のケースでは モーダルが閉じた後に表示されるViewControllerは MixiSampleViewController になるので、MixiSampleViewControllerにメソッドの定義を行います。モーダルとして表示されている MySecondViewController 上ではないので注意してください。
定義するメソッドには以下の制約を満たさなければなりません。
- 戻り値の型は
IBAction
型 - 引数は一つだけで、その型は
UIStoryboardSegue
への参照 (i.e.UIStoryboardSegue *
型)
これはstoryboardとこのメソッドを紐づけるための制約になります。メソッド名には特に制約はありません。 例えば以下のようにメソッドを定義してください。
- (IBAction)unwindToSampleViewController:(UIStoryboardSegue *)segue
{
// segue実行時にここが実行される
}
次はstoryboard上での操作になります。まずSegueをトリガーするためのボタンを配置します。MySecondViewControllerが表示されている時に行うアクションなのでMySecondViewController上にボタンを一つ配置してください。
次にMixiSampleViewController上部にアイコンが "Mixi Sample View Controller", "First Responder", "Exit" などが並んでいると思うのですが、この中からExitを右クリックするとパネルが表示されます。 ここに表示されているSegueがMixiSampleViewControllerに巻き戻ってくることのできるSegueとなります。おそらくここに先ほど定義したメソッドが表示されていると思います。このメソッドをドラッグアンドドロップで今追加したボタンと接続します。
このように接続を行うことができれば、作業は完了です。Runボタンから実行し、今追加したボタンをタップするとモーダルが閉じると思います。 無事閉じれば完了です。
ここまででモーダルを出現、閉じるという一連の画面遷移について学びました。この章での演習課題は、 この一連の画面遷移を再現することになります。
1.3章のプロジェクトの続きから行います。
新しいViewControllerのサブクラスを追加してstoryboardに追加してください。 次に、ボタンをタップしたときにこのViewControllerがモーダルで表示されるようなsegueをstoryboardのみで作ってください。
演習1.に引き続き、新しいボタンを追加し、ボタンがタップされた時に同じsegueが実行されるようにしてください。ただし演習1.とは異なり、コード上からsegueを実行してください。(i.e. performSegueWithIdentifier:sender:
を使う)
さらに引き続き、新しいボタンを追加し、ボタンがタップされたときに新しいViewControllerをsegueを使わず、 presentViewController:animated:completion:
を用いてモーダルが表示されるようにしてください。
新しいViewControllerにボタンを追加し、ボタンをタップしたら表示されたViewControllerが閉じるようにしてください。 ただし新しいViewController自身が閉じるのではなくdelegateを使うようにしてください。 またどのボタンをタップしても閉じるようにしてください。
※ヒント
segue経由で画面を表示するときのdelegateの設定は -prepareForSegue: sender:
の中で行います。
回答については iOSTraining/SampleProjects/1.4/MyFirstProject-answer をごらんください。
はじめに
-
導入
-
1.3 UIViewController1 UIViewController のカスタマイズ(xib, autoresizing)
-
UIKit 1 - container, rotate-
-
UIKit 2- UIView -
-
UIKit 3 - table view -
-
UIKit 4 - image and text -
-
ネットワーク処理
-
ローカルキャッシュと通知
-
Blocks, GCD
-
設計とデザインパターン
-
開発ツール
-
テスト
-
In-App Purchase
-
付録