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.
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'm coding a simple app with swift and I'm stuck at the following point, I have two Controllers that lead to another one, and when I click on the cancel button, it always lead to the root Controller, no matter from where I come.
I have a first controller (UIViewController), that go to the Navigation Controller of my target Controller (the one from which I would like to go back to the right calling Controller).
I have a second controller (UITableViewController), which go directly to my target Controller.
Here's the code of my Cancel button:
// MARK: - Navigation
#IBAction func lendingCancelButton(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways
let isPresentingInAddLendingMode = presentingViewController is UINavigationController
if isPresentingInAddLendingMode {
dismiss(animated: true, completion: nil)
} else if let owningNavigationController = navigationController {
owningNavigationController.popViewController(animated: true)
} else {
fatalError("the LendingViewController is not inside a navigation controller.")
}
}
If I correctly understood (you could then correct me if I'm wrong, I would learn something), it's testing if the ViewController that's presenting my target ViewController is a NavigationController.
So maybe that, as the second Controller (my UITableViewController) is not going through a NavigationController, so the last one calling my target view with a NavigationController is always the UIViewController.
Don't hesitate to tell me if it's not clear enough (too many times the word "Controller" in my post) or if you need additional code.
Try something like this
if let navigationController = presentingViewController as UINavigationController {
navigationController.popViewController(animated: true)
} else if let viewController = presentingViewController as UIViewController {
dismiss(animated: true, completion: nil)
} else {
fatalError("the LendingViewController is not inside a navigation controller.")
}
If i understood you want to use dismiss when you find a UIViewController and to pop the navigation when you find a UINavigationController right?
Ok so I finally found a way to make it working.
My tableViewController was embedded into a NavigationController. I removed it (since I could do without it, according to my need). From this View Controller, I draw a segue that "Show" my target view.
From my other ViewController (this one is embedded into a NavigationController), I draw a segue put that present modally my target view.
With the code provided in my initial post, it's working.
The only thing I didn't understand is why the NavigationController from my TableViewController was likely to cause it not working properly.
I have a view controller as my initial view controller. there's a button in it(GO button) which when the user taps, it should go to another view controller(let's call it Destination view controller with label 'This is where i wanna go'). Meanwhile i want to pass it through a Tabbar controller. The reason is i want to have tabbar in my navigation stack.
I wish to go directly to the Destination view controller while pressing go button but it should show the tab bar items at the bottom.
So for achieving this in FirstViewController didLoadMethod I checked a bool value and pushed the view controller to the Destination view controller. I achieved the result I.e when pressing the Go button it goes to the Destination view controller and has tab bar items at it's bottom.
But the problem since it passes through the Tabbarcontroller the FirstViewController is shown for some seconds and then it pushes to the Destination view controller. I wish to hide FirstViewController while this transition takes place.
How to achieve this?
Picture shows what i want. what can I do to hide firstviewcontroller while having it in navigation stack?
I think this can be done in a simple way -
In the first viewController of the tab bar has a viewDidLoad() function or you can use loadView() which is called before the viewDidLoad() function. Push to the next viewController in the function.
Put your push navigation code in one of those functions
*You cant see the current view coltroller, it will push the screen to your required viewcontroller before loading the tab bar initial view contoller.
Hope it will work for you.
or >>>> you can check it out
let storyboard = UIStoryboard(name: "your_storyBoard_name", bundle: nil)
let viewController1 = storyboard.instantiateViewController(withIdentifier: "firstViewController")
let viewController2 = storyboard.instantiateViewController(withIdentifier: "secondViewcontroller")
let controllers = [viewController1, viewController2]
self.navigationController!.setViewControllers(self.navigationController!.viewControllers + controllers, animated: true)
The effect you're trying to produce is hard to do in a storyboard. Programmatically you would simply create the Tabbar Controller (with its children) and the "This is where I want to go" Controller, and then ask the navigation controller to show both at the same time.
For example, after "Go" is tapped, this is the code I would run inside your first view controller:
let tabBarController = UITabBarController()
let finalDestination = UIViewController()
var viewControllers = self.navigationController?.viewControllers ?? []
viewControllers.append(tabBarController)
viewControllers.append(finalDestination)
self.navigationController?.setViewControllers(viewControllers, animated: true)
Given the structure you shown, where the view controller A is the root view controller of the TabBar, you should push the second view controller B on the navigation stack inside either willAppear or didLoad of view controller A, according to your personal business logic (flag, conditions, etc.).
The trick here is to use either pushViewController or setViewControllers with animated: false so that the navigation stack will be set immediately during willAppear/didLoad and it won't show the push animation of B over A. This way, at onDidAppear the layout will be already fully rendered in it's final state: with B at the top of the navigation stack and no animations in progress.
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)
I had to add a navigation controller to my app so that I could use the left drawer menu (using SWRevealViewController) but its messing up my segues. My initial design had a login screen that segued to one of 4 different scenes depending on an a status indicator.
Now that I had to add a navigation controller it looks like i'll have to take the user sequentially through through each screen in the stack until they reach the relevant one. Is there a way I can jump past the first screen or 2? Or a way to not show them as I navigate through.
I tried putting the performSegue in the viewWillLoad delegate method but the screen still loads before segueing to the next scene.
Add Storyboard IDs to all View/Navigation Controllers that will eventually be pushed:
Now to push the desired view it depends whether your current View Controller stands: within or outside the Navigation Controller's stack:
If your VC is already in the Navigation Controller stack
From your current ViewController push the desired view:
if let myViewController = self.storyboard?.instantiateViewControllerWithIdentifier("myViewController") as? MyViewControllerClassName {
self.navigationController?.pushViewController(myViewController, animated: true)
}
Note: as? MyViewControllerClassName is only required if your View Controller's class is not the default UIViewController but a custom one that extends it instead.
If your VC is NOT in the Navigation Controller stack
Same principle apply, only this time you need to push the Navigation Controller itself before pushing the desired View Controller:
if let newNavController = self.storyboard?.instantiateViewControllerWithIdentifier("myNavigationController") as? UINavigationController {
self.view.window?.rootViewController = newNavController
// Now push the desired VC as the example above, only this time your reference to your nav controller is different
if let myViewController = self.storyboard?.instantiateViewControllerWithIdentifier("myViewController") as? MyViewControllerClassName {
newNavController.pushViewController(myViewController, animated: true)
}
}