【Swift】ViewControllerとNavigationControllerの相互関係まとめ

## 概要
NavigationControllerが絡んでくると画面間の値の受け渡しが少し複雑になるので、この記事では様々な状況におけるViewControllerとNavigationControllerとの相互関係についてまとめてみました。

<img width="1408" alt="スクリーンショット 2019-02-21 17.38.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/163599/e83f46a7-6a66-314b-b902-c661460fbe07.png">

今回は画像のような

`NavigationController(1) -> FirstViewController -> SecondViewController -> NavigationController(2) -> ThirdViewController`

という構成を想定します。

ちなみに、FirstViewControllerからSecondViewControllerへは`Show`、SecondViewControllerからNavigationController(2)へは`Present Modally`のSegueが繋いであります。

以下
FirstViewController -> FirstVC
SecondViewController -> SecondVC
ThirdViewController -> ThirdVC


## NavigationController(1)とFirstVC
<img width="1408" alt="スクリーンショット 2019-02-21 17.38.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/163599/80cc009e-e19c-436a-d708-2061804066c0.png">


まず、アプリを立ち上げるとFirstVCが表示されます。

このとき、NavigationController(1)の`viewControllers`プロパティにはFirstVCが入っています。

また反対に、FirstVCの`navigationController`プロパティにはNavigationController(1)が要素として格納されているということになります。
NavigationController(1)はFirstVCの親ViewControllerなので、FirstVCの`parent`プロパティにもNavigationController(1)が同様に格納されます。

### NavigationController(1)からFirstVCの取得
NavigationController(1)からFirstVCを取得する手順は以下の通りです。

```.swift
// NavigationController(1)を以下で取得したとする
let nc = self.navigationController

let firstVC = nc.viewControllers[0] as! FirstViewController
```

FirstVCはNavigationController(1)のroot view controllerになるので、`viewControllers`プロパティの0番目の要素に格納されています。


### FirstVCからNavigationController(1)の取得
FirstVCからNavigationController(1)の取得を行う方法は以下のようになります。

ViewControllerがNavigationControllerに所有されていない場合は`navigationController`プロパティには`nil`が格納されるので注意しなければいけません。

```.swift
// FirstVCクラス内で
let nc = self.navigationController
```

また、親のViewControllerが格納されるプロパティである`parent`を用いて

```.swift
// FirstVCクラス内で
let nc = self.parentViewController as! UINavigationController
```

のようにも取得できます。親となるViewControllerが存在しない場合にはこちらも`nil`が格納されているので注意してください。

## NavigationController(1)とSecondVC
<img width="1408" alt="スクリーンショット 2019-02-21 17.38.32 2 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/163599/6ef5dc30-7e12-29d9-eae7-da03b2c6ff91.png">


FirstVCから`Show`で遷移した先がSecondVCです。

`Show`で遷移するとNavigationController(1)の`viewControllers`にSecondVCが追加で格納されます。`viewControllers`はスタックのように利用されるため、この時点では1番最後の要素である`viewControllers[1]`に格納されていることになります。

SecondVCにおいてもFirstVCの時と同様、`navigationController`プロパティと`parent`プロパティにNavigationController(1)が格納されます。

### NavigationController(1)からSecondVCの取得
NavigationController(1)からSecondVCを取得する際もFirstVCの時と同様の手順になります．

```.swift
let nc = self.navigationController

let vcNum = nc.viewControllers.count
let secondVC = nc.viewControllers[vcNum - 1] as! SecondViewController
```

### SecondVCからNavigationController(1)の取得

FirstVCの時と同じです。

```.swift
let nc = self.navigationController
```

または

```.swift
let nc = self.parentViewController as! UINavigationController
```

### 余談1 : SecondVCからFirstVCの取得
```.swift
let nc = self.navigationController

let vcNum = nc.viewControllers.count
let firstVC = nc.viewControllers[vcNum - 2] as! FirstViewController
```

`viewControllers`がスタックであることを考慮すれば1つ前の画面の取得は上記のようになります。

NavigationControllerの階層下で前の画面に値を渡す際はこの方法を使います。

### 余談2 : 遷移が`Present Modally`の場合
この場合は、NavigationController(1)とSecondVCの間には直接的な関係はありません。

SecondVCからNavigationController(1)を取得したい時は、

```.swift
let firstVC = self.presentingViewController as! FirstViewController
let nc = firstVC.navigationController
```

などのように一度FirstVCを経由すれば可能です。

## NavigationController(2)とThirdVC
<img width="1408" alt="スクリーンショット 2019-02-21 17.38.32 2 4.png" src="https://qiita-image-store.s3.amazonaws.com/0/163599/7f3cb144-b756-a1f5-41bb-999273ff593f.png">


「[NavigationController(1)とFirstVC](## NavigationController(1)とFirstVC)」と同じです。

## NavigationController(1)とThirdVC
<img width="1408" alt="スクリーンショット 2019-02-21 17.38.32 2 3.png" src="https://qiita-image-store.s3.amazonaws.com/0/163599/e4b9e2f7-da64-2c97-f8bf-ad907885453a.png">

NavigationController(1)とThirdVCの間には直接的な関係はありませんが、ThirdVCからNavigationController(1)を取得することは可能です。

### ThirdVCからNavigationController(1)の取得
UIViewControllerクラスの`presentingViewController`プロパティには、自分もしくは祖先となるViewControllerを`Present Modally`で表示したViewControllerが格納されます。

この場合、NavigationController(2)がNavigationController(1)によって表示されているため、

```.swift
let nc = self.presentingViewController as! UINavigationController
```

でNavigationController(1)を取得することができます。

NavigationController(1)からFirstViewControllerやSecondViewControllerを取得し、画面を戻るときに値を渡すことができます。

## まとめ
ViewControllerの相互関係を理解できれば画面間の値の受け渡しがスムーズにできるようになりますね。

ViewControllerのリファレンスに出てくるparentとancestorのニュアンスの違いがちょっと分かりにくかったです。

>UIViewControllerクラスのpresentingViewControllerプロパティには、自分もしくは祖先となるViewControllerをPresent Modallyで表示したViewControllerが格納されます。

ここの文言に違和感があったのはそのせいですね。切実に英語力が欲しい...。