【Swift】SplitViewControllerでiPadの設定アプリのような画面遷移をする

SplitViewControllerを使うと画面を分割して表示することができます。
そしたら次はiPadの設定アプリのような動きを実装したいってなりますよね？...ね？

↓こんな感じのやつ
![q1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763167/d6ae5ee4-9524-9563-e4ca-afa6fa50eab6.gif)



ですがこれ、一筋縄ではいきませんでした。
備忘録もかねて、誰かの一助となれば幸いです。

:::note warn
注意
既にSplitViewControllerで画面の分割表示が出来ていることを前提に話を進めます。
また、今回記述する内容はStoryBoardを使った実装例です。
:::

# splitViewControllerの構造
まずsplitViewControllerの全体の構造について簡単に触れておきます。
```mermaid
graph LR
    sp[SplitViewController]-->|primary|n_p[navigationController_primary];
    sp-->|secondary|n_s[navigationController_secondary];
    n_p-->|root|pv[primaryView];
    n_s-->|root|sv[secondaryView];
    sv-->|show|nv[nextView];
```
雑ですが大体こんな感じでしょうか。これでビルドすると
画面左 : primaryView
画面右：secondaryView
が表示されるハズ。

# 問題1:Secondary側のnavigationControllerが消える

さて、ここでsecondaryView内のTableCellのタップイベントにて以下のようにします。
```swift:secondaryView
// セルのタップイベント
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // 画面遷移
    self.navigationController?.pushViewController(nextView, animated: true)
}
```
これでもうiPadの設定アプリの動きそのものですね。

めでたしめでたし。

　
とは行きませんでした。ここで問題が発生します。
primaryView側を操作してsecondaryViewを切り替えた後に再度上記の.pushViewController(nextView, animated: true)を呼んでみましょう。

↓実際の動き
![q2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763167/7510aeb7-1ed7-c214-8fbe-b27f9a17f0cc.gif)



あれ？画面が変わらない。
というか**navigationController消えてね？**
さっきまであったSecondary側のnavigationControllerがnilになります。困った。

# 問題1の原因
splitViewControllerで画面遷移する際に.showDetailViewControllerメゾットを使っていました。
こんな感じ。
```swift:primaryView
let sb = UIStoryboard(name: "secondaryView", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "secondaryView") as! secondaryView
splitViewController?.showDetailViewController(vc, sender: nil)
```
しかしこれだとnavigationControllerが設定されていないsecondaryViewが表示されてしまいます。

# 問題1の解決策　...と新たな問題
そこで考えました。
**さっきまでいたnavigationControllerを保存すればいいんじゃね？**
と。

実際、最初にprimaryViewが表示されたときのsplitViewControllerを見てみると
```swift:primaryView
override func viewDidLoad() {
    super.viewDidLoad()
    
    print(splitViewController?.viewControllers.count) // 2
    print(splitViewController?.viewControllers[0]) // optional(UINavigationController)
    print(splitViewController?.viewControllers[1]) // optional(UINavigationController)
}
```
のようにnavigationControllerが2つあり、
そのうちの後者がsecondaryViewに対応しているnavigationControllerであることが分かりました。勝ったなガハハッ。

というわけで、primaryViewが表示されたタイミングでnavigationControllerを捕まえます。
```swift:primaryView
private var secondaryNavigationController: UINavigationController! // secondary側のUINavigationController

override func viewDidLoad() {
    super.viewDidLoad()

    // print(splitViewController?.viewControllers.count) // 2
    // print(splitViewController?.viewControllers[0]) // optional(UINavigationController)
    // print(splitViewController?.viewControllers[1]) // optional(UINavigationController)
    
    // NavigationControllerを記憶
    secondaryNavigationController = splitViewController?.viewControllers.last as? UINavigationController
}
```

そしてさっきと同じように.showDetailViewControllerメゾットを呼びます。

```swift:primaryView
// let sb = UIStoryboard(name: "secondaryView", bundle: nil)
// let vc = sb.instantiateViewController(withIdentifier: "secondaryView") as! secondaryView
// splitViewController?.showDetailViewController(vc, sender: nil)

splitViewController?.showDetailViewController(secondaryNavigationController, sender: nil)
```
するとどうでしょうか。

これで思惑通りになりましたね。
![q3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763167/b5bb3a80-f272-b855-1a3f-06d809852385.gif)
なってませんでした。また問題発生です。

# 問題2:汎用性がない
secondaryViewはその先のnextViewへ行けるようになりました。が、
このままでは**secondaryViewだけが次の階層に行けるチケット(*navigationController*)を得ただけ**になります。

**原因**：navigationController_secondaryの**rootViewController**がsecondaryViewと紐づけられているから。

# 問題2の解決策
そこで考えました。
**さっき保存したnavigationControllerのrootViewControllerを書き換えればいいんじゃね？**
と。
こんな感じで。
```swift:primaryView
    let sb = UIStoryboard(name: "tertiaryView", bundle: nil)
    let vc = sb.instantiateViewController(withIdentifier: "tertiaryView") as! tertiaryView
    
    // 画面遷移
    // rootViewControllerの設定
    secondaryNavigationController.setViewControllers(vc, animated: false)
    splitViewController?.showDetailViewController(secondaryNavigationController, sender: nil)
```
いい感じ。
ついでに毎回StoryboardからViewControllerを呼ぶのも面倒くさいので配列にまとめておくとさらにスッキリしてもっといい感じ。

```swift:primaryView
// 表示させるvc
private let arrayVC: [UIViewController] = [ 
    UIStoryboard(name: "secondary", bundle: nil).instantiateViewController(withIdentifier: "secondary")  as! secondary,
    UIStoryboard(name: "tertiary",  bundle: nil).instantiateViewController(withIdentifier: "tertiary")   as! tertiary,
    UIStoryboard(name: "quaternary",bundle: nil).instantiateViewController(withIdentifier: "quaternary") as! quaternary,
    UIStoryboard(name: "quinary",   bundle: nil).instantiateViewController(withIdentifier: "quinary")    as! quinary,
    UIStoryboard(name: "senary",    bundle: nil).instantiateViewController(withIdentifier: "senary")     as! senary,
]
```
```swift:primaryView
    // 画面遷移
    // rootViewControllerの設定
    secondaryNavigationController.setViewControllers([arrayVC[indexPath.section]], animated: false)
    // 表示
    splitViewController?.showDetailViewController(secondaryNavigationController, sender: nil)
```

↓実際の動き
![q4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763167/f2221bf4-d66a-0f19-ca24-658d577b2eb0.gif)


# 最後に
自分でやっておいてなんですが、まわりくどいですね。もっと簡単な方法があったら教えてください。
（今気付いたけど名言が逆になってる...まいっか:rolling_eyes:）