Present UINavigationController from UITabBarController tab root view - ios

I'd like to present a navigation controller from a tab bar controller's tab's root view controller and have the tabs from the tab bar controller visible.
Here's a photo of an example storyboard:
The tab bar controller has a relationship to the ItemOneViewController, which is implemented like this:
import UIKit
class ItemOneViewController: UIViewController {
private func presentNavigationController() {
let id = "NavController"
guard let vc = storyboard?.instantiateViewController(withIdentifier: id) else { return }
present(vc, animated: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentNavigationController()
}
}
I hoped that by presenting the navigation controller from the root view controller of a tab on a tab bar controller that the navigation controller would be presented behind the tabs, but when I run the described app, this is what the simulator looks like:
This is what the view hierarchy looks like:
I think I remember a previous colleague mentioning that a UINavigationController will replace the view stack, which makes sense but is there anyway to keep it or bring the UITabBarController with it?
I've tried adjusting the presentation style and context but haven't had any luck.

Try embedding your ItemOneViewController in Navigation Controller instead of embedding you ViewController VC in Navigation Controller.

Select ItemOneViewController from storyboard.
Go to the Editor and Embed with Navigation Controller
Your view hierarchy at Storyboard looks Like this :

Related

Use UINavigationController inside UITabbarController

I'm trying to Use UINavigationController inside a UITabbarController.
this is my Controllers Structure
-UITabbarController(InitialView)
-tabItemOne-DashboardController
-SomeButtons with StoryboardSegue-To-DifferentViewController
-tabItemTwo-OtherController
-tabItemThree-OtherController
Now I want to show the back button when some StoryboardSegue is performed in DashboardViewController.
Let's say I open the app and can see 4 tabbarItems on UITabbarController, in the first tabbar item I have DashboardViewController, in this DashVC I've 3 4 different buttons to show other viewcontrollers. So far so good, everything is working. but once the child viewcontroller from DashVC is on screen, I want to show a back button on the top as UINavigationController do.
I've tried to put the UINavigationController before UITabbarController but its not showing. i've tried to do embed it in DashboardVC but again its not showing.
Do i need to embed seperate UINavigationController with each of childViewControllers of DashboardVC?
Any help is appreciated.
So what you need is this
-UITabbarController(InitialView)
-NavigationController -tabItemOne-DashboardController
-SomeButtons with StoryboardSegue-To-DifferentViewController
-tabItemTwo-OtherController
-tabItemThree-OtherController
and in DashBoardController you need to add this code in
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden = false
}
this code is to hide the navigation bar in Dashboard and restoring when pushing another view controller so you back button is not hide.
also if you need the same functionality in the others view controller you should embebed in navigation controllers.
is not exactly your case but all navigation Controller are Childs of the UITabBarController that is the initial viewController.

How I could clean UINavigationBar transitions history?

I currently have parental "menu" TableView with UINavigationBar and from each cell there is a segues by reference outlet to 3 similar Views with different information.
In each View there is a buttons to other 2 Views.
With every button's segue opens another View.
The problem:
From every View UINavigationBar's back button returns me to previous View but i tries to make back button to "menu".
Additional Bar Button Item and segue from it makes very close effect but segue animation is not like in UINavigationController.
How I could clean UINavigationBar transitions history in segue to initial View?
You can try pop to root view controller or You can edit navigation controller viewControllers property and remove/add some VC in between.
You can try Unwind Segue mechanism too.
Here are some methods(function) that navigation controller providing for pop operations. They are returning optional UIViewController (intance) from it’s navigation stack, that is popped.
open func popViewController(animated: Bool) -> UIViewController? // Returns the popped controller.
open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? // Pops view controllers until the one specified is on top. Returns the popped controllers.
open func popToRootViewController(animated: Bool) -> [UIViewController]?
Here is sample code as a solution to your query::
// if you want to back to root of your app
if let rootNavigationController = self.window?.rootViewController as? UINavigationController {
rootNavigationController.popToRootViewControllerAnimated(true)
}
// But if you want to back to root of your current navigation
if let viewcontroller = self.storyboard?.instantiateViewController(withIdentifier: "NewViewController") as? NewViewController { // or instantiate view controller using any other method
viewcontroller.navigationController?.popToRootViewControllerAnimated(true)
}

Why tab bar is missing after presenting a new view controller?

I have created a simple tab bar with three views in storyboard. The tab bar works well, but when I try to show another view controller from a button within a tab, the new view is placed over the whole screen and also over the tab bar.
This is how I present the view so far when a button is pressed:
#IBAction func buttonPressed(_ sender: UIButton) {
let newVC = self.storyboard?.instantiateViewController(withIdentifier: "extraVC")
self.present(newVC!, animated: true, completion: nil)
}
The other idea I had was this:
self.tabBarController?.present(vc!, animated: true, completion: nil)
But this didn't work either.
So how can I present another view controller within the tab bar (so that the bottom bar is still shown)?
When you present a view controller modally, its presentation style will be "Full Screen" by default. What you want to do is have it do in this case is just cover part of the screen (the part where the presenting view controller is drawn.
One way to accomplish this is to:
Set the modalPresentationStyle for the presented view controller to be .currentContext or .overCurrentContext
In the view controller that will be presenting the modal, set its definesContext property to true.
These steps can be done either in Interface Builder by setting attributes on the segue and the view controller, or you can modify your code to include the following:
#IBAction func buttonPressed(_ sender: UIButton) {
let newVC = self.storyboard?.instantiateViewController(withIdentifier: "extraVC")
self.definesPresentationContext = true
newVC?.modalPresentationStyle = .overCurrentContext
self.present(newVC!, animated: true, completion: nil)
}
What this combination of properties does is:
Indicate for the presented view controller that you want it to be presented in a non-full screen context (some specific section of the screen)
Indicate that the presenting view controller is in the section of the screen / the context you want the modal to be drawn according to.
More details can be found in the Apple Documentation
When you are using present method, the ViewController is presented modally and covers your UITabBarConntroller. Instead of showing your view modally you can embed every first view controller in your TabBar into UINavigationController and then use method pushViewController to push it onto stack. You will have your TabBar visible and nice looking animation for free.
In Xcode, I created a new project using the Tabbed App template to illustrate the solution above. This will create a project with a tabbar controller and two view controllers. I added a button with the title "view page" to the first view controller and embedded a navigation controller from the storyboard.
The storyboard will look like this after making the above changes:
In the FirstViewController.swift file, I created an IBAction for the button with the following code that will create another view controller called DetailViewController, with the title Favorites and a background color of orange. I used the navigation controller to present it by pushing it onto the navigation controller stack.
#IBAction func viewPageButtonTapped(_ sender: UIButton) {
print("viewPageButtonTapped")
let pinkViewController = DetailViewController()
pinkViewController.title = "Favorites"
pinkViewController.view.backgroundColor = UIColor.orange
navigationController?.pushViewController(pinkViewController, animated: true)
}
When I run the project on the simulator, I got the desired result. Hope this helps give you some ideas.
In your viewController do:
self.tabBarController?.present(nextViewController, animated: true/false, completion: {})

Getting back to main ViewController of the Tab Bar after moving between tabs

I am developing an iOS app in Swift 2.3, XCode 8.0. My app has 4 tabs and to each of the tabs - 4 different View Controllers are connected. Inside the 4 View Controllers I have embedded Navigation controllers.
My requirement is that when a user selects tab 1 -> goes to ViewController 1 -> Next the user can go to 2nd View Controller as there is a navigation controller.
But when the user selects the second tab and then comes back to the first tab, instead of showing the first View Controller that is directly attached to tab 1, the View Controller that was last opened with the back button is shown.
How can I move directly move to the View Controller attached to the first tab?
The embedded navigation controller should automatically keep track of the stack for you regardless of which tab the user is in with the tab bar controller. Make sure each tab has its own navigation controller embedded into it instead of having the entire tab bar controller embedded in one navigation controller.
For example, if you implemented this programmatically:
let viewController1 = UIViewController()
let navigationController1 = UINavigationController()
navigationController1.setViewControllers([viewController1], animated: true)
let viewController2 = UIViewController()
let tabBarController1 = UITabBarController()
tabBarController1.setViewControllers([navigationController1, viewController2], animated: true)
Here the navigationController1 will keep track of which viewController is on top of the stack regardless of where the user is in the tab bar controller.
Thanks, I found the solution -
This works for me -
In my main tab bar viewController, I made it a delegate of both UITabBarController, UITabBarControllerDelegate and then implemented the didSelectViewController method of UITabBarControllerDelegate as below -
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
let index : Int = (tabBarController.viewControllers?.indexOf(viewController))!
let navigationController = viewController as? UINavigationController
navigationController?.popToRootViewControllerAnimated(false)
}

Navigation Bar Not Showing after Adding NavigationController

I have an existing UITableViewController that I've embedded in a NavigationController. However, the Navigation Bar is not showing when I present the view.
Presenting the TableViewController (its Storyboard id is: SelectServicesController) :
if let selectServicesController = self.storyboard?.instantiateViewControllerWithIdentifier("SelectServicesController") as? UITableViewController {
self.navigationController?.presentViewController(selectServicesController, animated: true, completion: nil)
}
This is what it looks like when I build (nav bar does not show):
So I just did this and at fist could not get it to show up at all. Then Figured it out, You just need to select the navigation controller and set it to be the ✅is initial View Controller
This is what your storyboard should look like
Then to make everything show up I added this to my viewDidLoad of the view the Navigation controller is presenting. This step is more optional.
self.navigationController?.navigationBar.barTintColor = UIColor.redColor()
self.navigationController?.navigationBar.tintColor = UIColor.blackColor()
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName:UIColor.blackColor()]
navigationController?.navigationBar.hidden = false
And this is what it looks like
mmmm Red on black 🤓 Hope that helps you.
You're presenting a UITableViewController, which doesn't have a navigation controller as a parent (even though your Storyboard has it first, you're not actually using it).
You can fix this by doing something like this:
if let selectServicesController = self.storyboard?.instantiateViewControllerWithIdentifier("SelectServicesController") as? UITableViewController {
let navigationController = UINavigationController(rootViewController: selectServicesController)
self.navigationController?.presentViewController(navigationController, animated: true, completion: nil)
}
Or by setting the navigation controller as the initial view controller of the storyboard and then calling it like this:
if let selectServicesController = self.storyboard?.instantiateInitialViewController() {
self.navigationController?.presentViewController(selectServicesController, animated: true, completion: nil)
}
I encountered the same problem. I solved it by Changing the segue to the navigation controller that embeds the View Controller I want to display.
Hopefully it would work for you.
Let me know if it is a bad practice.
let storyboard = UIStoryboard(name: "Expense", bundle: Bundle(for: PTCAddExpenseViewController.self))
let controller = storyboard.instantiateViewController(withIdentifier:"AddExpense") as! PTCAddExpenseViewController
let navigationController = UINavigationController(rootViewController: controller)
self.present(navigationController, animated: true, completion: nil)
Adding this works for me:
self.navigationController?.isNavigationBarHidden = false
You're presenting the table view controller directly, not its navigation controller. If you mark the nav controller as the initial view controller (tick the "Is initial view controller" box in the attributes inspector), then you can instantiate and show it by:
if let selectServicesNavController = self.storyboard?.instantiateInitialViewController() as? UINavigationController {
// if you're pushing it onto an existing nav controller
self.navigationController?.presentViewController(selectServicesNavController, animated: true, completion: nil)
// if not (and this is probably the case), set the nav controller as your window's rootViewController
UIApplication.sharedApplication().keyWindow.rootViewController = selectServicesNavController
}
My guess is Xcode is ignoring the fact that your table view controller is embedded in navigation controller when presenting your table view controller with the following code:
if let selectServicesController = self.storyboard?.instantiateViewControllerWithIdentifier("SelectServicesController") as? UITableViewController {
self.navigationController?.presentViewController(selectServicesController, animated: true, completion: nil)
}
Instead, I would suggest you modify the Top Bar setting under Simulated Metrics to suit your needs or instantiate your navigation controller instead (the latter is preferred and recommended)
In your code, change this line
self.navigationController?.presentViewController(selectServicesController, animated: true, completion: nil)
to this
self.presentViewController(selectServicesController, animated: true, completion: nil)
I read in one of your comments you want to present the table view controller modally, with the navigation bar showing. We can do this using the Storyboard. From the view controller that should display this table view controller modally, Ctrl+Drag from the view controller to the Navigation Controller of the Table View Controller. Then, select Present Modally. Set an Identifier for the segue. Then, in your code for the view controller that is presenting the table view controller modally, call:
self.performSegueWithIdentifier("YourSegueIdentifier", sender: nil)
Another way to do this without having to use any code is if the modal presentation is being triggered by something like a button. Then, you can Ctrl+Drag from that button to the Navigation Controller and select Present Modally.
Or it might be this!!
I had the same problem: the navigation bar was showing on the root view in Storyboard, but when running the Simulator - there was no navigation bar at the top of the views. This solved it:
Navigation Controller > Navigation Bar > UNCHECK Translucent (it is checked by default). This did two things:
My Navigation Bar shows on all subsequent views.
The topmost subview is now at Y=0, and not Y=64.

Resources