画面遷移でパラメータの受け渡しを行う(Objective-c)

# 目的
- 画面を遷移する
- 前の画面でに入力した値が、次の画面で表示される
- 画面の値を変更せずに元の画面へunwindした場合、前回入力した値が保持されている
- 次の画面で、値を変更した場合、unwindした場合、変更後の値が元画面に反映されている

- 前回の画面遷移がベースになります。
- http://qiita.com/yukihigasi/items/36480371d772207895d1
- 今回はパラメータの入力フォームが付き、受け渡しができるようにする部分を説明します

# 環境
- XCode 8.x
- Objective-C
- MacOS Sierra


***ソースはgitHub上へ公開されておりますので、必要に応じてご利用下さい
https://github.com/yukihigasi/objective_c_study_sample***

# 手順
## 画面
- 画面遷移の方法は前回の内容と同じで、NaivigationViewControllerを利用します
- 今回は二つの画面***ViewController***と***SecondViewController***があります
- 前画面の入力を、次の遷移先でも表示します
- 逆に先画面で入力に変更した場合、前画面に戻るとその変更がされた状態になります
![スクリーンショット 2017-05-03 19.38.52.png](https://qiita-image-store.s3.amazonaws.com/0/57455/ed81dab4-5c90-18db-9e57-5271e078659e.png)

### 元画面(ViewController)
- Segueは ***Forward*** というidentiferをつけています（任意の名前でOK）
- Navigation上のForwardボタンを押下すると遷移が起こります
![スクリーンショット 2017-05-03 19.34.50.png](https://qiita-image-store.s3.amazonaws.com/0/57455/7ab41dba-480d-ed0f-f645-af6d3812ace1.png)

- 画面には、以下のような画面名を確認する V1　というラベルと
- 二つの入力項目があり、それぞれ ***l1v  l2v*** とします

![スクリーンショット 2017-05-03 19.46.31.png](https://qiita-image-store.s3.amazonaws.com/0/57455/55954aa9-7220-a0ae-dea3-336553a8d1ab.png)

- ソース状ではIBOutletの UITextField型のインスタンスで、以下の変数とひもづきます

```objc:ViewController.h
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *l1v;
@property (weak, nonatomic) IBOutlet UITextField *l2v;

@end
```

### 先画面(SecondViewController)
- unwindセグエは、***RETURN***というidentiferをもち
- Actionには前画面の***unwindToTop***とひもづいています
- unwindは、Backボタンで実行されるように設定されています（デフォのNavigationButtonではなく）
![スクリーンショット 2017-05-03 19.35.05.png](https://qiita-image-store.s3.amazonaws.com/0/57455/663dfa3f-540e-f5a5-c606-b8f520eafdcb.png)

- SecondViewControllerは、
- 画面名を確認するV2というラベルと
- 前画面の表示内容をラベルで表示する ***L1***と
- 前画面の表示内容を編集可能なTextFieldで表示する***L2***の項目があり
![スクリーンショット 2017-05-03 19.50.48.png](https://qiita-image-store.s3.amazonaws.com/0/57455/8dc67775-acb4-02c3-a317-83f68e827aa1.png)

- ソース状以下のような変数にひもづいています

```objc:SecondViewController.h
@interface SecandViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *l1v;
@property (weak, nonatomic) IBOutlet UILabel *l2v;
@property (strong, nonatomic) TransitionObj* transitionObj;
@property (weak, nonatomic) IBOutlet UITextField *f2v;

@end

```

## ソース
- 画面ができたので実装に移ります

### 元画面(ViewController)
- ヘッダファイルでは、パラメータを保持するTransitionObjectというクラスを保持しています次に解説します
- TransitionObjectは、パラメータの変数をまとめて管理するために用意したもので、iOS上に置いて必須のものではありません、任意でやっています

```objc:ViewController.h
#import <UIKit/UIKit.h>
#import "TransitionObj.h"

@interface ViewController : UIViewController
@property (strong, nonatomic) TransitionObj* transitionObj;

@end
```

- 実装部分は以下のようになります

```objc:ViewController.m

#import "ViewController.h"

#import "SecandViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *l1v;
@property (weak, nonatomic) IBOutlet UITextField *l2v;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    if(self.transitionObj){
        self.l1v.text=self.transitionObj.l1;
        self.l2v.text=self.transitionObj.l2;
    }
    
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    
    if ([segue.identifier isEqualToString:@"FOWARD"]) {
        //ここでパラメータを渡す
        self.transitionObj = [[TransitionObj alloc] init];
        self.transitionObj.l1 = self.l1v.text;
        self.transitionObj.l2 = self.l2v.text;
        
        SecandViewController *dist = segue.destinationViewController;
        dist.transitionObj = self.transitionObj;
    }
    
    
}
- (IBAction)unwindToTop:(UIStoryboardSegue *)segue{
    self.l1v.text=self.transitionObj.l1;
    self.l2v.text=self.transitionObj.l2;
}
- (IBAction)next:(id)sender {
     [self performSegueWithIdentifier:@"FOWARD" sender:self];
}

@end
```
- まずは次の画面へ値を受けわたすために必要な部分を説明します
- 画面遷移をするときは、遷移直前に***prepareForSegue:sender***メソッドが呼ばれます
- この中でパラメータの設定など画面遷移の準備をします
- 
```objc
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    
    if ([segue.identifier isEqualToString:@"FOWARD"]) {
        //ここでパラメータを渡す
        self.transitionObj = [[TransitionObj alloc] init];
        self.transitionObj.l1 = self.l1v.text;
        self.transitionObj.l2 = self.l2v.text;
        
        SecandViewController *dist = segue.destinationViewController;
        dist.transitionObj = self.transitionObj;
    }
    
    
}
```

- 最初の **if ([segue.identifier isEqualToString:@"FOWARD"]) {** は、
- セグエの種類をidentiferで識別して処理を分けています
- 複数の遷移先がある場合、どのsegueでの遷移なのかを識別して画面遷移を準備することができます
- 今回は***FOWARD***セグエ当てであるため、上記の中に実装します

- 次に値をTransitionObjectのプロパティへ受け渡します

- ここで、次の画面のオブジェクトを呼び出し、dist変数に保持します

```objc
SecandViewController *dist = segue.destinationViewController;
```
- そうして、受けわたすパラメータの準備ができたら、次の画面へ受け渡します

```objc
dist.transitionObj = self.transitionObj;
```

- 次の画面のプロパティのtransitonObjへ、先ほどパラメータを設定したtransitonObjを受け渡します
- 繰り返しになりますが、TransitionObjectというクラスを作らなくても、直接次の画面の持っている変数へ代入しても問題ありません、一つの変数にまとめるため、任意でこのクラスを用意しています


### 画面のパラメータを保持する共通クラス(TransitionObject)
- transitionObjectを用意するのは、以下の都合で任意で用意しています
- 画面へ入力された値を保持するため
- 遷移時に受けわたすパラメータを一つにするため
- 実体は、変数があるだけです

```objc:TransitonObject.h
#import <Foundation/Foundation.h>

@interface TransitionObj : NSObject
@property (nonatomic) NSString *l1;
@property (nonatomic) NSString *l2;
@property (nonatomic) NSString *l3;

@end

```

```objc:TransitonObject.m
#import "TransitionObj.h"

@implementation TransitionObj

@end

```

- 本来ですと、ここでは単純に変数が直接定義されてしまっていますが

```
@property (nonatomic) NSString *l1;
@property (nonatomic) NSString *l2;
@property (nonatomic) NSString *l3;
```

- 画面ごとに画面の値全てを持つクラスを用意して置いて、クラスをtransitionObjectにもつようにすれば、遷移する全ての画面の値を保持した状態のクラスを作ることができます
- そうすると、画面がどういう形で遷移しても、その画面に入力されていた値をすぐに、シンプルに取り出すことができますし、見通しがよくなリマス


### 先画面(SecondViewController)
- 画面に表示するラベルと、値を受けわたす***transitionObj***が定義されています
- この***transitionObj***は先ほど***segue.destinationViewController;***で呼び出された画面オブジェクトへ代入したものと同じものです。なので、これには前の画面で入れたパラメータが入っています
 
```objc:SecondViewController.h
#import <UIKit/UIKit.h>
#import "TransitionObj.h"
@interface SecandViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *l1v;
@property (weak, nonatomic) IBOutlet UILabel *l2v;
@property (strong, nonatomic) TransitionObj* transitionObj;
@property (weak, nonatomic) IBOutlet UITextField *f2v;

@end

```

- 具体的な実装は以下になります

```objc:SecondViewController.m

#import <Foundation/Foundation.h>
#import "SecandViewController.h"
#import "TransitionObj.h"
#import "ViewController.h"

@interface SecandViewController ()
@end

@implementation SecandViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.l1v.text=self.transitionObj.l1;
    self.l2v.text=self.transitionObj.l2;
    
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    
    if ([segue.identifier isEqualToString:@"RETURN"]) {
        //ここでパラメータを渡す
        
        self.transitionObj.l2 = self.f2v.text;
        
        ViewController *dist = segue.destinationViewController;
        dist.transitionObj = self.transitionObj;
    }
    
    
}

@end

```

- 画面へ、受け渡されたパラメータを表示します
- 場所は、画面の初期化処理***viewDidLoad***の中で行います

```
- (void)viewDidLoad {
    [super viewDidLoad];
    self.l1v.text=self.transitionObj.l1;
    self.l2v.text=self.transitionObj.l2;
    
}
```
- 前画面から引き継いだ***transitionObj***の値を、ラベル等へ設定しています
- これで、前画面のフィールドに入力した値がこの画面で表示されるようになりました

![スクリーンショット 2017-05-03 20.26.54.png](https://qiita-image-store.s3.amazonaws.com/0/57455/78c61a56-ba09-33e1-ea0d-6842950e6eaf.png)

- 数字1と２を入力してForwardボタンを押下します

![スクリーンショット 2017-05-03 20.27.37.png](https://qiita-image-store.s3.amazonaws.com/0/57455/489c5083-41a3-8b30-d763-f25f21ac24b5.png)

- V2画面で、先ほどの値が受け渡されています

### 戻る(unwind)時のパラメータの扱い
- 次は、V2からV1へ戻る時の動きです。この画面では以下のことができます
- Backボタンを押下するとunwindが行われる
- V1はV2への遷移前の値を保持している
- V2のL2のラベルは、右のテキストフィールドの値で上書きされて前画面に戻る
![スクリーンショット 2017-05-03 20.31.01.png](https://qiita-image-store.s3.amazonaws.com/0/57455/9dfdf153-619a-94b2-b8b9-1a82ba78c2a1.png)

- unwind時も***prepareForSegue***が呼ばれます。
- 今回、unwindセグエのidentiferは***RETURN***ですので、RETURNで識別し遷移前の処理をします
- この時も、***segue.destinationViewController;***で遷移先の画面を取得することができ、その画面へtransitionObjectを受け渡します

```objc:SecandViewController.m
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    
    if ([segue.identifier isEqualToString:@"RETURN"]) {
        //ここでパラメータを渡す
        
        self.transitionObj.l2 = self.f2v.text;
        
        ViewController *dist = segue.destinationViewController;
        dist.transitionObj = self.transitionObj;
    }
}

```

- 遷移先で値を受け取ります
- unwindセグエの指定アクションはunwindToTopでしたので、戻り先のアクション名のメソッドが呼び出されます
- ここで画面上のオブジェクトへ値を受け渡します

```objc:ViewController.m
- (IBAction)unwindToTop:(UIStoryboardSegue *)segue{
    self.l1v.text=self.transitionObj.l1;
    self.l2v.text=self.transitionObj.l2;
}

```

- V2画面で二つ目のラベル(2)を、3と入力してunwindをします
 
![スクリーンショット 2017-05-03 20.27.03.png](https://qiita-image-store.s3.amazonaws.com/0/57455/58f283c3-3444-c7b7-77a3-0d4705bfacba.png)

- V1へ戻ると、一つ目のラベルの値が保持されており
- 二つ目のラベルの値が更新されていることが確認できました。

![スクリーンショット 2017-05-03 20.27.27.png](https://qiita-image-store.s3.amazonaws.com/0/57455/977f1cf6-7cef-3a1c-3299-17aac024fe7c.png)


- 以上が、パラメータの受け渡しになります。