For the purpose of this question, I'm showing a stripped down version of my view hierarchy. My app contains a UITabBarController as the base. Each tab's top most view controller is a navigation controller and it has view controllers embedded in each of them.
Let's take the first tab.
UITabBarController -> UINavigationController -> UITableViewController -> UIViewController
Let's say the UITableViewController instance is some sort of a list and the UIViewController is the detail view. When the user taps on an item from the list, it takes you to the detail view. And when that happens I have set the UIViewController's hidesBottomBarWhenPushed property to true so that the tabbar at the bottom would hide when the user is in the detail view.
My app receives push notifications. When tapped on them, it should open directly into the detail view. I can get it to navigate there. But the issue is the tabbar at the bottom is still visible!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
window = UIWindow(frame: UIScreen.main.bounds)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "TabBarController") as! TabBarController
if openingFromPush {
let firstNavigationController = storyboard.instantiateViewController(withIdentifier: "FirstNavigationController") as! UINavigationController
let tableViewController = storyboard.instantiateViewController(withIdentifier: "TableViewController") as! TableViewController
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.hidesBottomBarWhenPushed = true
firstNavigationController.viewControllers = [tableViewController, viewController]
tabBarController.viewControllers?[0] = firstNavigationController
// tabBarController.tabBar.isHidden = true
window?.rootViewController = tabBarController
} else {
window?.rootViewController = tabBarController
}
window?.makeKeyAndVisible()
return true
}
I set that same hidesBottomBarWhenPushed property to true in the when I instantiate the view controller but that doesn't seem to have any effect. I even tried straight up hiding the tabbar like this tabBarController.tabBar.isHidden = true but that doesn't do anything at all either.
I can't figure how how to resolve this. Any help would be appreciated.
I attached a sample Xcode project here as well if that helps.
You can use this code for pushing detail view controller:
if openingFromPush {
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.hidesBottomBarWhenPushed = true
if let nvc = tabBarController.viewControllers?[0] as? UINavigationController {
nvc.pushViewController(viewController, animated: false)
}
window?.rootViewController = tabBarController
}
You don't need to init navigation view controller and table view controller again its already inside tab bar controller
Related
I am trying to skip the login screen and go straight to the MainViewController when the user is logged in. However the problem is that I have a Tab Bar Controller and Navigation Controller between the login and the main vc. After extensive search I wrote the below code
func showMainViewController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mainViewController: MainViewController = storyboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
let navigationController = UINavigationController(rootViewController: mainViewController)
//It removes all view controllers from the navigation controller then sets the new root view controller and it pops.
window?.rootViewController = navigationController
// //Navigation bar is hidden
// navigationController.isNavigationBarHidden = true
}
However it fails to show the tab bar view controller. Any help is appreciated.
try this
func showMainViewController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mainViewController: MainViewController = storyboard.instantiateViewController(withIdentifier: "TabBar")
window?.rootViewController = mainViewController
window?.makeKeyAndVisible()
}
you should instantiate TabBar no main viewController. since is instantiate inmediatrly is if first index or set the selected index
I am having a problem when performing segue from my AppDelegate. I am using this code to do a segue from appdelagate:
let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
let viewController: MyViewController = storyboard.instantiateViewController(withIdentifier: "myviewcontroller") as! MyViewController
let rootViewController = self.window!.rootViewController as! UINavigationController
rootViewController.show(viewController, sender: self)
When I use this, my UITabBar is removed. I want to segue to a ViewController that is not a TabBar item and retain my UITabbar. I am also using navigation. How should I approach this?
To show the tab , you need to do the push from 1 of the tab vcs , and it should be embeded inside a navigation and use
let nav = self.window!.rootViewController as! UINavigationController
if let tab = nav.viewControllers.first as? UITabBarController ,
let innerNav = tab.viewControllers.first as? UINavigationController {
innerNav.pushViewController(viewController,animated:true)
}
I want to provide a log on view controller so my users can authenticate prior to being presented the tab bar controller. In the code displayed below, I get the error "Could not cast value of type 'LogInViewController' to 'UITabBarController'. Apple documentation listed:
When deploying a tab bar interface, you must install this view as the
root of your window. Unlike other view controllers, a tab bar
interface should never be installed as a child of another view
controller.
I am stumped and my implementation is complicated when I am propagating core data across the 5 view controllers (e.g. controller1.coreDataStack = coreDataStack, declared as a property in the AppDelegate class) that is part of the tab bar controller. Can I get some help on how should I transition the user from the log in screen to one of the tabbed view controllers? Any input would be appreciated.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Login View Controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
self.window?.rootViewController = loginVC
// TabBar Controller
let tabController = window!.rootViewController as! UITabBarController
if let tabViewControllers = tabController.viewControllers {
// First tab (only one so far...)
let navController = tabViewControllers[0] as! UINavigationController
let controller1 = navController.viewControllers.first as! FirstViewController
controller1.coreDataStack = coreDataStack
}
}
You got error because trying to force unwrap LogInViewController to UITabBarController.
For your design flow store loginStatus of user in database an check condition on this before setting the window.rootViewController, if user is logged-in then show tabController, otherwise show LogInViewController.
let userLogined = GET LOGIN STATUS FROM DATABSE
if userLogined{
// Initiate Tabbar Controller object
let tabController = INITIATE_TABBAR_CONTROLLER
let tabViewControllers = tabController.viewControllers
// First tab (only one so far...)
let navController = tabViewControllers[0] as! UINavigationController
let controller1 = navController.viewControllers.first as! FirstViewController
controller1.coreDataStack = coreDataStack
self.window?.rootViewController = tabController
}else{
// Login View Controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
self.window?.rootViewController = loginVC
}
As an alternative method, how about always setting the rootViewController to the tabbed bar controller. Then if the user is logged out, modally present the login view controller from the tabbed bar controller. Once the user has successfully logged in, dismiss the login view controller.
I've used this method many times on many apps.
I've made the launch screen the root view while my app makes a request to firebase and then reassign the root view once the request has been completed. Unfortunately when I do this my navigation bar disappears or is covered up by the new root view. When I run the simulator I can see the navigation bar briefly and then it get covered up by my TableViewController. How do I keep this from happening?
Here is my code from the AppDelegate where I make all of this happen:
var window: UIWindow?
let searchManager = SearchManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
makeRootViewLaunchScreen()
FirebaseApp.configure()
searchManager.getMosaicTitles { results in
self.searchManager.listOfMosaics = results
self.stopDisplayingLaunchScreen()
}
// Adds border to bottom of the nav bar
UINavigationBar.appearance().shadowImage = UIImage.imageWithColor(color: UIColor(red:0.00, green:0.87, blue:0.39, alpha:1.0))
// Override point for customization after application launch.
return true
}
func makeRootViewLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "launchScreen")
UIApplication.shared.keyWindow?.rootViewController = viewController
}
func stopDisplayingLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? SearchResultsTableViewController
viewController?.searchManager = searchManager
UIApplication.shared.keyWindow?.rootViewController = viewController
UIApplication.shared.keyWindow?.rootViewController?.navigationController?.isNavigationBarHidden = false
}
As you can see I tried to use UIApplication.shared.keyWindow?.rootViewController?.navigationController?.isNavigationBarHidden = false to force the navigation bar to appear but it doesn't. My app still looks like this:
You are setting a UIViewController as your Root controller. What you want to do is set a UINavigationController as your Root controller.
Either create a new navigation controller in your storyboard and load that one instead of loading "centralViewController", or modify your function like this:
func stopDisplayingLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? SearchResultsTableViewController {
viewController.searchManager = searchManager
// create a new UINavigationController
let newNavVC = UINavigationController()
// set the "root" VC of the NavVC to your SearchResultsTableViewController
newNavVC.setViewControllers([viewController], animated: false)
// use the new NavVC as the new rootViewController
UIApplication.shared.keyWindow?.rootViewController = newNavVC
UIApplication.shared.keyWindow?.rootViewController?.navigationController?.isNavigationBarHidden = false
}
}
Note: not tested, but this should get you on your way.
Edit: Another approach...
Instead of swapping the root controller of the key window, create a ViewController that indicates you are initializing / retrieving your data. That VC can be the "root" of a Navigation Controller.
Put your searchManager.getMosaicTitles function in that VC. When it has finished, replace the current "launch" controller in your Nav Controller with your TableVC. It will look something like this:
// note: searchManager will have to be a reference back to AppDelegate.searchManager
searchManager.getMosaicTitles { results in
self.searchManager.listOfMosaics = results
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? SearchResultsTableViewController {
viewController.searchManager = searchManager
// replace the current Nav VC stack with the new SearchResultsTableViewController
self.navigationController.setViewControllers([viewController], animated: false)
}
}
To prevent the lag between when the navigation bar controller appears and the tableView appears check out the following. Prevent navigation bar from disappearing without lag
UI flow:
AppDelegate
-->
LoginViewController (not in the storyboard)
-->
navigation controller (in the storyboard)
-->
PFQueryTableViewController (in the storyboard) named "OrdersVC"
This is the navigation controller with OrdersVC:
This is my AppDelegate:
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// ...
// initial VC
let VC = LoginViewController()
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window!.rootViewController = VC
window!.makeKeyAndVisible()
return true
}
The above works fine. Then, from LoginViewController, I am then trying to display my storyboard's initial VC which is a navigation controller hosting a PFQueryTableViewController. Note that LoginViewController is not in the storyboard.
let destVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("OrdersVC") as! UITableViewController
// will throw "unexpectedly found nil"
let navController = UINavigationController(rootViewController: destVC)
navController.pushViewController(destVC, animated: true)
// will throw "unexpectedly found nil"
self.presentViewController(navController, animated: true, completion: nil)
Problem is that in my PFQueryTableViewController's viewDidLoad and viewDidAppear the following statement is always nil:
// navitaionController is nil
self.navigationController!.navigationBar.translucent = false
So how can I correctly instantiate PFQueryTableViewController inside its navigation controller?
You are instantiating the OrdersVC instead of instantiating the navigation controller into which it is embedded and which is the "initial" view controller of your storyboard. Use instantiateInitialViewController instead of using the identifier.
let nav = storyboard.instantiateInitialViewController()
self.window!.rootViewController = nav
The reason for the confusion is that you are "unlinking" the initial view controller from the storyboard with your login controller. You have to add the initial view controller back to the main window.