2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【UIKit】UITabBarControllerとUINavigationControllerを組合わせた場合のNavigation Item操作方法

Posted at

はじめに

UITabBarControllerUINavigationControllerを組合わせた場合のNavigation Itemの操作方法にハマったので、整理してみました

  • Navigation -> TabBar -> Navigation -> ViewController
  • Navigation -> TabBar -> ViewController

上記2パターンで実装した時のNavigation Itemはどのように指定できるのか確認しました

実装

UINavigationController : Navigation
UITabBarController : TabBar
と表現します

Navigation -> TabBar -> Navigation -> ViewController

Menu画面

TabBar画面
スクリーンショット 2022-06-20 14.23.21.png

Menu画面 -> TabBar画面 の処理

Menu画面
final class MenuViewController: UIViewController {
    @objc
    func gameButtonSelected() {
        let storyboard = UIStoryboard(name: "Game", bundle: nil)
        let tabBarCon = storyboard.instantiateViewController(identifier: "Game")
        self.navigationController?.pushViewController(tabBarCon, animated: true)
        self.navigationController?.setNavigationBarHidden(true, animated: true)

        // ViewControllerを取得したいなら
        // guard let tabBar = tabBarCon as? UITabBarController else { return }
        // guard let navBarCon = tabBar.viewControllers?[0] as? UINavigationController else { return }
        // guard let destinationVC = navBarCon.topViewController as? ___ViewController else { return }
    }
}

このままではNavigationBarが二重で表示されてしまうので、
最初のNavigation -> TabBarの遷移でself.navigationController?.setNavigationBarHidden(true, animated: true)としました

Tab Bar Itemはタブごとのnavigationで(storyboard上で)定義しています

Navigation Itemについても各ViewControllerで定義したものが表示されます

各ViewController
final class GameViewSettingViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        tabBarController?.navigationItem.title = R.string.localizable.gameViewSetting()
        navigationItem.hidesBackButton = true
        navigationItem.leftBarButtonItem = self.createBarButtonItem(image: UIImage.named("questionmark.circle"), select: #selector(self.questionBarButtonItem))
    }
    
    private func createBarButtonItem(image: UIImage, select: Selector) -> UIBarButtonItem {
        let button = UIButton(type: .custom)
        button.setImage(image, for: .normal)
        button.addTarget(self, action: safeSelect, for: .touchUpInside)
        let barButtonItem = UIBarButtonItem(customView: button)
        return barButtonItem
    }

TabBar画面のViewControllerからさらに画面遷移させる

TabBarViewControllerからself.navigationController?.pushViewControllerで画面遷移させたところ遷移先画面のナビゲーションバーが表示されませんでした

そこで遷移時の処理にself.navigationController?.setNavigationBarHidden(false, animated: true)を追加したところ無事表示されました

TabBar画面のViewController
final class GameViewSettingViewController: UIViewController {
    @objc
    func questionBarButtonItem() {
        let storyboard = UIStoryboard(name: "HowToUseDetail", bundle: nil)
        let vc = storyboard.instantiateViewController(identifier: "HowToUseDetail")
        self.navigationController?.pushViewController(vc, animated: true)
        self.navigationController?.setNavigationBarHidden(false, animated: true)
    }
}

が、今度は遷移元の画面へ戻った時にナビゲーションバーが2重で表示されてしまいました

遷移元へ戻る時にnavigationController?.setNavigationBarHidden(true, animated: true)を追加したところ期待した表示になりました

遷移先のViewController
    @objc
    func back() {
        navigationController?.popViewController(animated: true)
        navigationController?.setNavigationBarHidden(true, animated: true)
    }

しかし、期待通りの動きにはなりましたが、都度.setNavigationBarHiddenをさせて無理矢理表示をコントロールしている点にはとても違和感を感じるため、TabBar以降のNavigationを抜いての実装も試してみました

Navigation -> TabBar -> ViewController

Menu画面からのTabBar画面の遷移処理部分を修正します(TabBarはコード実装へ変更)

Menu画面
final class MenuViewController: UIViewController {
    @objc
    func gameButtonSelected() {
        let tabBarCon = UITabBarController()
        let gamePlayerVC = R.storyboard.gameDPlayer.instantiateInitialViewController()!
        let gameDataVC = R.storyboard.gameData.instantiateInitialViewController()!
        let gameResultVC = R.storyboard.gameResult.instantiateInitialViewController()!
        tabBarCon.setViewControllers([gamePlayerVC, gameDataVC, gameResultVC], animated: true)
        self.navigationController?.setNavigationBarHidden(false, animated: true)
        self.navigationController?.pushViewController(tabBarCon, animated: true)
    }
}

TabBar後のNavigationを消したことにより
Tab Bar Itemは各ViewControllerで定義したものが表示されますが、Navigation Itemが反映されなくなりました

ここは要注意箇所なようで、TabBarのnavigationItemに定義してあげる必要があるようです

また、実際のUIを考えると、各タブごとにNavigation Itemは変えるべき(少なくともタイトルは変わる?)であり
以下のようなtabBarController?.navigationItemを修正する処理を各ViewControllerで行う必要があります

  • tabBarController?.navigationItem.title =
  • tabBarController?.navigationItem.hidesBackButton =
  • tabBarController?.navigationItem.leftBarButtonItem =

ただし、
この処理をViewDidLoadでやるとタブ遷移後に戻った場合は二度目が呼ばれないので、
ViewWillAppearで処理する必要があります

各ViewController
final class GameViewSettingViewController: UIViewController {
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        tabBarController?.navigationItem.title = R.string.localizable.gameViewSetting()
        tabBarController?.navigationItem.hidesBackButton = true
        tabBarController?.navigationItem.leftBarButtonItem = self.createBarButtonItem(image: UIImage.named("questionmark.circle"), select: #selector(self.questionBarButtonItem))
    }
}

(もしTab Bar Itemのタイトルを変える場合は、storyboard上だけでなく
tabBarController?.tabBarItem.title = でもできます)

TabBar画面のViewControllerからさらに画面遷移させる

self.navigationController?.pushViewControllerが使えなくなり
self.tabBarController?.parent as? UINavigationControllerで一度Navigationを取得してから.pushViewControllerするようにします

self.navigationController?.setNavigationBarHiddenの処理を考えなくて済むのが良いところです

TabBar画面のViewController
final class GameViewSettingViewController: UIViewController {
    @objc
    func questionBarButtonItem() {
        let storyboard = UIStoryboard(name: "HowToUseDetail", bundle: nil)
        let vc = storyboard.instantiateViewController(identifier: "HowToUseDetail")
        let navigationCon = self.tabBarController?.parent as? UINavigationController
        navigationCon?.pushViewController(vc, animated: true)
    }
}

おわりに

この記事を書いている途中に見かけましたが、もしかするとUITabBarControllerを途中に表示するのはよくないのかな??
アプリの使い勝手としては、ぜひこのまま使いたいところですが、アンチパターンなら作り変える必要もあるような。

UIViewController, UINavigationController, UITabBarControllerのメソッドをもっと調べればよりよい実装ができる気もしているので、引き続き改善を目指して行こうと思います。
(そもそもUITabBarController使わないで実装できたりするかも?と思ったり)

参考

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?