Storyboard の画面遷移

むかし自分の日記に書いたやつを貼ってみます。

前提として、FirstViewController, SecondViewController, ThirdViewController の三つの View Controller があり、UINavigationController などは使わずにモーダルに遷移するものとします。

## Segue を使わない画面遷移
Storyboard に FirstViewController と SecondViewController を配置し、それぞれ適当な ID (とりあえず FirstScene と SecondScene) を付けます。
FirstScene と SecondScene にそれぞれボタンなどを置き、その IBAction には次のように書きます。

```FirstViewController.m
- (IBAction)openSecondScene:(id)sender
{
    // FirstScene と SecondScene が同じ Storyboard にある場合
    SecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"SecondScene"];

    // FirstScene と SecondScene が異なる Storyboard にある場合
    //UIStoryboard *secondStoryboard = [UIStoryboard storyboardWithName:@"Second" bundle:nil];
    //SecondViewController *secondViewController = [secondStoryboard instantiateViewControllerWithIdentifier:@"SecondScene"];

    // SecondScene が異なる Storyboard の Initial View Controller である場合
    // (この場合、SecondViewController の ID (SecondScene など) は不要。)
    //UIStoryboard *secondStoryboard = [UIStoryboard storyboardWithName:@"Second" bundle:nil];
    //SecondViewController *secondViewController = [secondStoryboard instantiateInitialViewController];

    [self presentViewController:secondViewController animated:YES completion:nil];
}
```

```SecondViewController.m
- (IBAction)closeSecondScene:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
}
```

ほとんど xib と同じ感覚で使えますね。

ちなみに、Scene ってのは View Controller と View 階層を組み合わせた概念で、Storyboard では同じView Controller クラスに対して、別々の View を組み合わせたものを複数持つことができます。

## Segue を使った画面遷移
FirstScene のボタンなどから SecondScene への Segue を作れば、コードなしで画面遷移できます。
戻る場合は注意が必要で、SecondScene の戻るボタンから FirstScene への Segue (Modal などの) を作ると、戻るのではなく、FirstScene を新しく作ってそれを開くことになっちゃいます。

iOS 5 では、戻る場合は Segue を使わない画面遷移と同様に、IBAction で dismissViewControllerAnimated:completion: を呼ぶ必要があります。

```SecondViewController.m
- (IBAction)closeSecondScene:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
}
```

iOS 6 以降は、Unwind Segue というのが導入されましたが、戻り先の View Controller に戻る際に呼ばれるメソッドを用意して、Storyboard で Unwind Segue を作る時に指定する必要があります。
また、戻り先は直前の View Controller に限らず、例えば FirstScene → SecondScene → ThirdScene と遷移した状態で、FirstViewController にしかないメソッドを指定すれば、一気に FirstScene に戻ることができます。

```FirstViewController.m
- (IBAction)unwindToFirstScene:(UIStoryboardSegue *)unwindSegue
{
    // 中身は空っぽでよい。
}
```

つまり、(Modal から) 戻る場合は結局コードが必要ってことで、あんまり嬉しくないような。
また、Deplyoment Target が iOS 5 の場合、Storyboard に Unwind Segue が存在するだけでビルド時にエラーになるので、実行時に切り替えることもできません。

## 画面遷移時の情報のやり取り
Segue を使って画面遷移する際に、FirstScene から SecondScene に情報を渡すには、prepareForSegue:sender: メソッドを実装します。
このメソッドは、FirstViewController から遷移するすべての Segue に対して呼ばれるので、Segue を区別するためにあらかじめ各 Segue に ID を付けておく必要があります。

```FirstViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"OpenSecondScene"]) {
        SecondViewController *secondViewController = segue.destinationViewController;
        secondViewController.someProperty = @"value";
    }
}
```

Segue を使わずに dismissViewControllerAnimated:completion: メソッドで戻る際に、SecondScene から FirstScene に情報を渡すには、あらかじめ FirstViewController のポインタ (できればプロトコルを定義して) を SecondViewController に渡しておいて、戻る際に FirstViewController のメソッドを呼ぶ必要がありました。

Unwind Segue を使えば、戻り先で呼ばれるメソッドを指定できるので、これを使います。

```FirstViewController.m
- (IBAction)unwindToFirstScene:(UIStoryboardSegue *)unwindSegue
{
    // このメソッドを複数の Unwind Segue で使い回す場合は、ID で識別する必要がある
    //if ([unwindSegue.identifier isEqualToString:@"UnwindFromSecondScene"]) {
        SecondViewController *secondViewController = unwindSegue.sourceViewController;
        // ここで secondViewController のプロパティやメソッドを利用
    //}
}
```

Unwind Segue では、この場合 SecondScene は遷移元 (sourceViewController) になることに注意。

## 画面遷移するかどうかの判断
ボタンなどを押されても、ある条件が成り立たなければ画面遷移したくない、という場合は、shouldPerformSegueWithIdentifier:sender: メソッドを実装して NO を返します。
(条件が成り立たない場合は何もしないなら、そもそもボタンを無効化すべきって話しもあるけど。)

```FirstViewController.m
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
    if ([identifier isEqualToString:@"OpenSecondScene"]) {
        if (/* 画面遷移したくない条件 */) {
            // 遷移する代わりの処理を書くこともできる。
            return NO;
        }
    }
    return YES;
}
```

他の Segue と同様、Unwind Segue に対しても、戻る元の View Controller (この場合は SecondViewController) の shouldPerformSegueWithIdentifier:sender: メソッドが呼ばれるので、戻りたくない場合には NO を返します。

```SecondViewController.m
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
    if ([identifier isEqualToString:@"UnwindFromSecondScene"]) {
        if (/* 戻りたくない条件 */) {
            // 遷移する代わりの処理を書くこともできる。
            return NO;
        }
    }
    return YES;
}
```

shouldPerformSegueWithIdentifier:sender: メソッドでは同期的な処理しかできないので、UIAlertView を出して画面遷移するかどうかをユーザーに確認する、というような使い方はできません。この場合は、後述する performSegueWithIdentifier:sender: メソッドを使います。

## コードによる画面遷移
ボタンなどの操作によって直接遷移するのではなく、コードによって画面遷移したい場合があります。例えば、次のような場合です。

- ボタンなどの普通のタップ操作ではなく、複雑なイベント処理や、Gesture Recognizer の結果で遷移したい
- タイマーなど、ユーザーの操作以外で遷移したい
- UIAlertView やネットワーク通信など、非同期処理の結果によって遷移したい
- 遷移先を (ある条件によって SecondScene または ThirdScene のように) 切り替えたい

このような場合、ボタンなどを起点にしてではなく、View Controller を起点として Segue を作り、ID を設定した上で、画面遷移するには performSegueWithIdentifier:sender: メソッドを使います。

例えば、UIAlertView の結果によって遷移する場合は次のようにします。

```FirstViewController.m
- (IBAction)openSecondScene:(id)sender
{
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Second Scene" message:@"Are you sure?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Open!", nil];
    [alertView show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1) {
        [self performSegueWithIdentifier:@"OpenSecondScene" sender:nil];
    }
}
```

また、遷移先を切り替えたい場合は次のようにします。

```FirstViewController.m
- (IBAction)openSecondOrThirdScene:(id)sender
{
    if (/* SecondScene に遷移する条件 */) {
        [self performSegueWithIdentifier:@"OpenSecondScene" sender:nil];
    } else { // ThirdScene に遷移
        [self performSegueWithIdentifier:@"OpenThirdScene" sender:nil];
    }
}
```

もちろん、Unwind Segue も同様に performSegueWithIdentifier:sender: メソッドで戻ることができます。

なお、performSegueWithIdentifier:sender: メソッドを使う場合も、遷移先に情報を受け渡すには、prepareForSegue:sender: メソッドを実装する必要があります。特に、通信の結果に基づいて遷移する場合などは、受け渡す情報をいったんインスタンス変数に格納する必要があります。