はじめに
UITabBarControllerとUINavigationControllerを組合わせた場合のNavigation Itemの操作方法にハマったので、整理してみました
-
Navigation->TabBar->Navigation->ViewController -
Navigation->TabBar->ViewController
上記2パターンで実装した時のNavigation Itemはどのように指定できるのか確認しました
実装
UINavigationController : Navigation
UITabBarController : TabBar
と表現します
Navigation -> TabBar -> Navigation -> ViewController
Menu画面 -> TabBar画面 の処理
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で定義したものが表示されます
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からさらに画面遷移させる
TabBarのViewControllerからself.navigationController?.pushViewControllerで画面遷移させたところ遷移先画面のナビゲーションバーが表示されませんでした
そこで遷移時の処理にself.navigationController?.setNavigationBarHidden(false, animated: true)を追加したところ無事表示されました
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)を追加したところ期待した表示になりました
@objc
func back() {
navigationController?.popViewController(animated: true)
navigationController?.setNavigationBarHidden(true, animated: true)
}
しかし、期待通りの動きにはなりましたが、都度.setNavigationBarHiddenをさせて無理矢理表示をコントロールしている点にはとても違和感を感じるため、TabBar以降のNavigationを抜いての実装も試してみました
Navigation -> TabBar -> ViewController
Menu画面からのTabBar画面の遷移処理部分を修正します(TabBarはコード実装へ変更)
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で処理する必要があります
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の処理を考えなくて済むのが良いところです
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使わないで実装できたりするかも?と思ったり)
参考

