SplitViewControllerを使うと画面を分割して表示することができます。
そしたら次はiPadの設定アプリのような動きを実装したいってなりますよね?...ね?
ですがこれ、一筋縄ではいきませんでした。
備忘録もかねて、誰かの一助となれば幸いです。
注意
既にSplitViewControllerで画面の分割表示が出来ていることを前提に話を進めます。
また、今回記述する内容はStoryBoardを使った実装例です。
splitViewControllerの構造
まずsplitViewControllerの全体の構造について簡単に触れておきます。
雑ですが大体こんな感じでしょうか。これでビルドすると
画面左 : primaryView
画面右:secondaryView
が表示されるハズ。
問題1:Secondary側のnavigationControllerが消える
さて、ここでsecondaryView内のTableCellのタップイベントにて以下のようにします。
// セルのタップイベント
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 画面遷移
self.navigationController?.pushViewController(nextView, animated: true)
}
これでもうiPadの設定アプリの動きそのものですね。
めでたしめでたし。
とは行きませんでした。ここで問題が発生します。
primaryView側を操作してsecondaryViewを切り替えた後に再度上記の.pushViewController(nextView, animated: true)を呼んでみましょう。
あれ?画面が変わらない。
というかnavigationController消えてね?
さっきまであったSecondary側のnavigationControllerがnilになります。困った。
問題1の原因
splitViewControllerで画面遷移する際に.showDetailViewControllerメゾットを使っていました。
こんな感じ。
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を見てみると
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を捕まえます。
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メゾットを呼びます。
// 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)
するとどうでしょうか。
これで思惑通りになりましたね。
なってませんでした。また問題発生です。
問題2:汎用性がない
secondaryViewはその先のnextViewへ行けるようになりました。が、
このままではsecondaryViewだけが次の階層に行けるチケット(navigationController)を得ただけになります。
原因:navigationController_secondaryのrootViewControllerがsecondaryViewと紐づけられているから。
問題2の解決策
そこで考えました。
さっき保存したnavigationControllerのrootViewControllerを書き換えればいいんじゃね?
と。
こんな感じで。
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を呼ぶのも面倒くさいので配列にまとめておくとさらにスッキリしてもっといい感じ。
// 表示させる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,
]
// 画面遷移
// rootViewControllerの設定
secondaryNavigationController.setViewControllers([arrayVC[indexPath.section]], animated: false)
// 表示
splitViewController?.showDetailViewController(secondaryNavigationController, sender: nil)
最後に
自分でやっておいてなんですが、まわりくどいですね。もっと簡単な方法があったら教えてください。
(今気付いたけど名言が逆になってる...まいっか)