はじめに
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
使わないで実装できたりするかも?と思ったり)
参考