1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-04-05

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

↓こんな感じのやつ
q1.gif

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

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

splitViewControllerの構造

まずsplitViewControllerの全体の構造について簡単に触れておきます。

雑ですが大体こんな感じでしょうか。これでビルドすると
画面左 : primaryView
画面右:secondaryView
が表示されるハズ。

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

さて、ここでsecondaryView内のTableCellのタップイベントにて以下のようにします。

secondaryView
// セルのタップイベント
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // 画面遷移
    self.navigationController?.pushViewController(nextView, animated: true)
}

これでもうiPadの設定アプリの動きそのものですね。

めでたしめでたし。

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

↓実際の動き
q2.gif

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

問題1の原因

splitViewControllerで画面遷移する際に.showDetailViewControllerメゾットを使っていました。
こんな感じ。

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を見てみると

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を捕まえます。

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メゾットを呼びます。

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
なってませんでした。また問題発生です。

問題2:汎用性がない

secondaryViewはその先のnextViewへ行けるようになりました。が、
このままではsecondaryViewだけが次の階層に行けるチケット(navigationController)を得ただけになります。

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

問題2の解決策

そこで考えました。
さっき保存したnavigationControllerのrootViewControllerを書き換えればいいんじゃね?
と。
こんな感じで。

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を呼ぶのも面倒くさいので配列にまとめておくとさらにスッキリしてもっといい感じ。

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,
]
primaryView
    // 画面遷移
    // rootViewControllerの設定
    secondaryNavigationController.setViewControllers([arrayVC[indexPath.section]], animated: false)
    // 表示
    splitViewController?.showDetailViewController(secondaryNavigationController, sender: nil)

↓実際の動き
q4.gif

最後に

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?