So I have 3 VCs embedded in a navigation controller and a tab bar controller. However, whenever I add a segue from the 3rd VC to the 1st VC, the navigation bar and tab bar disappear from the storyboard. So I tried adding a segue programmatically on the 3rd VC swift file as such:
let collectionVC = self.storyboard?.instantiateViewController(withIdentifier: "collectionVC") as! CollectionViewController
let navigationVC = UINavigationController(rootViewController: collectionVC)
self.present(navigationVC, animated: true, completion: nil)
Unfortunately, with the added code above, the tab bar is still missing but the navigation bar is there. I hope someone could help me.
If I understood correctly your problem, you are using a UINavigationController and you are trying to go from VC3 to VC1, which means that you have a navigation stack like this:
VC1 -> VC2 -> VC3
If you want to go back to VC1, you can use:
navigationController?.popToRootViewControllerAnimated(true)
You will still have your tab bar, your navigation bar and - most important - you won't create another instance of VC1. This is the correct - and easiest - way of dealing with a UINavigationController.
Related
I have a tab bar controller with three table view controllers and the second VC is embedded in a navigation Controller. in the second VC, I made the tabBar hidden using this line self.tabBarController?.tabBar.isHidden = true and I created a bar button to go back to the first view controller which is the "home" VC using segue with modal presentation.
Screenshot of my StoryBoard
My problem is after hitting the back button and going back to home VC from the second VC, the tabBar is still hidden even though I put self.tabBarController?.tabBar.isHidden = false in the home VC's viewWillAppear method and the second VC's viewWillDisappear method.
Here is the result that I expected vs what I got
expected home VC
result home VC
How can I make the Tab Bar show?
When you are using the modal presentation segue, you are creating a completely new instance of HomeViewController. The new HomeViewController is not linked to the TabBarController in your hierarchy.
Here's you initial view hierarchy:
TabBarController
-> HomeVC
-> CreateVC (Navigation Controller)
-> CreateQuizVC
-> SavedVC
Now after tapping the back button you'll get the following:
TabBarController
-> HomeVC
-> CreateVC (Navigation Controller)
-> CreateQuizVC
-> HomeVC(2)
-> SavedVC
What you could do is, instead of using the segue to go back, add an IBAction in your code to set the selectedIndex of the TabBar programatically, and link the Back UIBarButtonItem to this IBAction.
#IBAction func backButtonAction(_ backButton: UIBarButtonItem) {
// Keep in mind that the CreateQuizVC is embeded in a NavigationController.
// The NavigationController is the child of the TabBarController
navigationController?.tabBarController?.selectedIndex = 0
navigationController?.tabBarController?.tabBar.isHidden = false
}
However, my suggestion is you use the TabBar as it's intended by Apple. Don't hide it while you're presenting your CreateQuizVC, and use the TabBar to navigate between the tabs. This will help with user experience, since everybody on iOS is expecting this behaviour from a TabBar.
I know this question has been asked so many times on the SO, but I am asking this just because it is relevantly different and also some answers are out dated and some are in Objective-c which I can not understand properly.
USE CASE:
I have a UITabBar controller, and it is working fine. Let say I have 4 tabs in it and user click on the button given in the Tab 4. now on it I have to open some series of View Controllers. let say User has following patteren to follow.
4.A-->4.B--> 4.C and can go back to first like so: 4.C-->4.B-->4.A
And finally User must also be allowed to go back to Tab4 after closing 4.A view controller
WHAT I DID:
I am able to open the View controller using this code.
let VC1 = storyboard.instantiateViewController(withIdentifier :"myVcId") as! UIViewController
let navController = UINavigationController(rootViewController: VC1) // Creating a navigation controller with VC1 at the root of the navigation stack.
self.present(navController, animated:true, completion: nil)
This is opening a navigation controller and having me navigate the view controller as per my requirements but I want following thing too
WHAT I WANT: As I am presenting the Navigation Controller modally, I want to show the back button at very first view controller, and I want that if user select back button it kills all the Navigation controller and go the previous view controller where he came from
You need to select the 4th tab and embed it inside a navigation then do
let vc = storyboard.instantiateViewController(withIdentifier :"myVcId") as! UIViewController
self.navigationController?.pushViewController(vc, animated: true)
then you'll see a back on that first vc , to return to that tab do
self.navigationController?.popToRootViewController(animated: true)
As far as I understood, your structure looks like this:
A TabBarController, with ViewControllers as tabs, and from them you present a navigation controller with other ViewControllers, like this:
(TabBar) -vc-> (View) -present-> (NavController) -root-vc-> (View)
I have two suggestions for making it easier to handle.
First option
Use navigation controllers as tabs for your TabBarController, and make your ViewControllers that are tabs their root view controllers.
(TabBar) -vc-> (NavController) -root-vc-> (View) -push-> (View)
Second option
Another option is to use a navigation controller as your initial view controller, and make your tab bar this navigation controller's root view controller.
(NavController) -root-vc-> (TabBar) -view-> (View) -push-> (View)
Both options should work, and the first one should be a little easier to handle.
You need to instantiate the navigationController with 4A as the rootViewController.
Then, in the viewDidLoad for 4A, you need to instantiate 4B, and push it to your navigationController.
Then, finally, in your 4B viewController's viewDidLoad you need to instantiate 4C and push it to your navigationController's stack.
P.S.: Push all the viewControllers without animations.
This should be achieving your strange scenario.
EDITED:
You need your 4th tab from your tabBar to be a navigationController,
and its root view Controller to be the initial VC which will then push
your new navController, otherwise you won't have the back button.
So, your stack should be something like this:
4CVC
|-(push)
4BVC
|-(push)
4AVC
|-(push)
newNavController
|-(push)
someVC
|
navController
|
tab1 tab2 tab3 tab4
|
tabBar
I have a viewController related to a Tab Bar Controller: the first one.
Clicking on a cell of its tableview, I'll show programmatically another viewController that's not linked to the first viewController with no segue (because of right reasons).
Now, my goal is to present/instantiate the second viewController related to the tab bar mentioned at the beginning of this question.
If I'll use this:
let vc=storyboard?.instantiateViewController(withIdentifier: "offerteView") as! SecondViewController
It'll be presented the mentioned viewController without the tab bar of course.
How can I solve it?
Embed the first view controller in a navigation controller and use its pushViewController function to show the second view controller.
let vc = storyboard?.instantiateViewController(withIdentifier: "offerteView") as! SecondViewController
navigationController?.pushViewController(vc, animated: true)
when using tab bars the view controllers are called on the basis of their Index and because of this the tab bars are still maintained and this can be done like this.
self.tabBarController!.selectedViewController! = self.tabBarController!.viewControllers[3]
where [3] is the index position of the View Controller.
or
self.tabBarController.selectedIndex = 1;
//Hope it was helpful. Happy Coding.
As you can see below, the notificationsVC is a part of the TabBarController which is embedded in a navigationContoller(lets call it first nC). Then theres a segue from notificationsVC to the second navigationController which will show the messagesVC.
There's a back button in messagesVC which when pressed should go back to notificationsVC
func backbutton() {
navigationController?.popViewControllerAnimated(true)
}
Now this is obviously not working because the navigationController will get the nearest NC and pop the VC in its stack but it won't let me go back to the notificationsVC.
Any other alternative?, although I've tried this with no success as well.
self.dismissViewControllerAnimated(true, completion: nil);
More detailed view
Also I'm using the JSQMessagesViewController library to show the messages in messagesVC which shouldn't matter but still worth mentioning. Thanks for your time!
You can access first NavigationViewController by asking it from TabBarViewController like in code below:
tabBarController?.navigationController?.popViewControllerAnimated(true)
Also asking navigation controller from you second navigation controller should work:
navigationController?.navigationController?.popViewControllerAnimated(true)
Your Navigation controller has only one VC i.e MessagesVc. So when you pop it,there is no other VC in the Navigation Controller's stack which can be presented. Your NotificationsVC is not in the Navigation controller's stack.
So I suggest you to do like this on back button click:
tabBarController?.selectedIndex = Index_Of_NotificationsVC
Try : -
let nVC = self.navigationController?.tabBarController?.navigationController?.storyboard?.instantiateViewControllerWithIdentifier("NotificationStoryboardVC_ID") as! NotificationVC
navigationController?.tabBarController?.navigationController?.pushViewController(nVC, animated: true)
When presenting or dismissing VC, I do not want to keep hiding and showing tabBar because it creates a poor user experience. Instead, I want present the next VC straight over the tab bar such that when I dismiss the nextVC by dragging slowly from left to right, I can see the tabBar hidden behind the view (As shown in image below)
Note, my app has two tabs with two VCs(VCA,VCB) associated to it. Both VC also have navigation bar embedded. VCA segues to VCA1 and VCB segues to VCB1. At the moment, inside VCA and VCB I am calling the following function to segue with some hiding and unhiding done when viewWillappear (Code below).
self.navigationController?.showViewController(vc, sender: self)
// Inside ViewWillAppear Only reappear the tab bar if we successfully enter Discover VC (To prevent drag back half way causing tab bar to cause comment entry to be floating). This code check if we have successfully enters DiscoverVC
if let tc = transitionCoordinator() {
if tc.initiallyInteractive() == true {
tc.notifyWhenInteractionEndsUsingBlock({(context: UIViewControllerTransitionCoordinatorContext) -> Void in
if context.isCancelled() {
// do nothing!
}
else {
// not cancelled, do it
self.tabbarController.tabBar.hidden = false
}
})
} else {
// not interactive, do it
self.tabbarController.tabBar.hidden = false
}
} else {
// not interactive, do it
self.tabbarController.tabBar.hidden = false
}
----------Working solution from GOKUL-----------
Gokul's answer is close to spot on. I have played with his solution and came up with the following improvement to eliminate the need to have a redundant VC and also eliminate the initial VC being shown for a brief second before tabVC appears. But without Gokul, I would never ever come up with this!!
Additionally, Gokul's method would create a bug for me because even though I do have a initial "normal" VC as LoginVC before tabVC is shown. This loginVC is ONLY the rootVC if the user needs to login. So by setting the rootVC to tabVC in most cases, the navVC will never be registered.
The solution is to embed navigation controller and tabBar controller to one VC. But it ONLY works if the navVC is before the TabBarVC. I am not sure why but the only way that allowed me to have navVC-> tabVC-> VC1/VC2 is to embed VC1 with a navVC first than click on VC1 again to embed tabVC (It wouldn't allow me to insert one before tabVC and I also had to click the VC1 again after embedding the NavVC).
For your requirement we need to make some small changes in your given view hierarchy
Let me explain step by step,
To meet your requirement we have to add a UIViewController(let's say InitialVC) embedded with a UINavigationController and make it as initial viewcontroller.
Then add a UITabbarController with 2 VC (VCA,VCB) // IMPORTANT: Without any navigationcontroller embedded.
Add a segue between InitalVC and TabbarController with an unique identifier(ex: Initial)
In viewWillAppear of InitalVC perform segue as below (InitialVC is unnecessary to our design we are using this just to bridge navigationController and tabbarController).
self.performSegueWithIdentifier("Initial", sender: nil)
In TabbarControllerclass hide your back button, this ensures that InitialVC is unreachable.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
}
Now add a segue from a button between VCA and VCA1, thats it build and run you will see VCA1 presenting over VCA's tabbar.
What we have changed?
Instead of adding UINavigationController inside UITabbarController we have done vice versa. We can't directly add Tabbar inside navigation to do that we are using InitialVC between them.
Result:
1st way is create a image of the tabbar using UIGraphicsGetImageFromCurrentImageContext and set it on the bottom of the other view...
2nd way is show the next view in another new window that is above the tabbar, that way you wont need to hide the tabbar anymore, but seems like its in the navigation controller so this way doesnt seems available
Hiding and unhiding the tab bar is unnecessary. You only need to embed the UITabBarController inside the UINavigationController. That is, UINavigationController as the initial vc, UITabBarController as the root vc of UINavigationController.