I have a main tabBarController and would like to present a viewController modally when a certain tabBarItem is tapped.
I am loading the viewControllers in my tabBarController as...
func setupViewControllers() {
self.tabBar.isHidden = false
if let firstVC = storyboard?.instantiateViewController(withIdentifier: "first") as? FirstViewController, let secondVC = storyboard?.instantiateViewController(withIdentifier: "second") as? SecondViewController, let thirdVC = storyboard?.instantiateViewController(withIdentifier: "third") as? ThirdViewController, let fourthVC = storyboard?.instantiateViewController(withIdentifier: "fourth") as? FourthViewController, let fifthVC = storyboard?.instantiateViewController(withIdentifier: "fifth") as? FifthViewController {
let firstNavController = UINavigationController(rootViewController: firstVC)
let secondNavController = UINavigationController(rootViewController: secondVC)
let fourthNavController = UINavigationController(rootViewController: fourthVC)
let fifthNavController = UINavigationController(rootViewController: fifthVC)
firstNavController.tabBarItem.image = image
secondNavController.tabBarItem.image = image
fourthNavController.tabBarItem.image = image
fifthNavController.tabBarItem.image = image
thirdVC.tabBarItem.image = image
tabBar.tintColor = nil
//Load tabBar viewControllers
viewControllers = [homeNavController, postNavController, plusMenuVC, meetupNavController, profileNavController]
}
}
I then conformed the tabBarViewController to the UITabBarControllerDelegate to invoke the method...
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if tabBarController.selectedIndex == 2, let thirdVC = viewController as? ThirdViewController {
thirdVC.modalPresentationStyle = .overFullScreen
thirdVC.modalTransitionStyle = .crossDissolve
present(thirdVC, animated: true, completion: nil)
return false
} else { return true }
}
The above however never gets triggered. I tried setting in viewDidLoad
self.delegate = self
I tried setting the root navigation controllers and its ancestor tabBarController delegate to self.
Nothing seemed to work and I hope someone can guide me as I've been unable to debug and find an existing solution...
UPDATE
So I created a dummyThirdVC to replace the thirdVC in the setupViewControllers() function. In the dummyThirdVC, I conformed to UITabBarControllerDelegate and in viewDidLoad I set the self.tabBarController.delegate = self. Then I took the delegate method and entered it into this dummyThirdVC, where inside this delegate method, I instantiated the real thirdVC to present.
The delegate method finally triggers properly, but my issue now is, the dummyThirdVC and its view must first load and appear for the delegate to be set and triggered thereafter.
How can I not show the dummyThirdVC and immediately just present the instantiated, real thirdVC? I had tried dummyThirdVC.viewDidLoad() in my setupViewControllers function to no avail...
I believe your check is wrong. You're checking for selectedIndex to be 2, but the selectedIndex 's value will always be the actual tab bar's selected index, and not the index that is going to be selected, so you're basically never going to reach selectedIndex as 2.
You also cannot present a view controller that is already active, and thirdVC is already active in your tabBar, thus you will get an error. A workaround for this is to use a viewController as placeholder for image and title in the tab bar and instantiate another one for presenting.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is ThirdViewController {
let vcToPresent = storyboard?.instantiateViewController(withIdentifier: "third") as? ThirdViewController
vcToPresent.modalPresentationStyle = .overFullScreen
vcToPresent.modalTransitionStyle = .crossDissolve
present(vcToPresent, animated: true, completion: nil)
return false
}
return true
}
Related
I have a UITabBarController which has 4 tabs. I want to show different view controllers for second tab bar item. Depending on the condition I want to show the view controllers for that tab bar item.
I wrote following code :
UITabbarController -
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if item.tag == 2 {
if UserDefaults.standard.bool(forKey: "FirstTimeUser") == true {
// let vc1 = storyboard?.instantiateViewController(identifier: "CreateNewProjectViewController") as! CreateNewProjectViewController
// self.navigationController?.pushViewController(vc1, animated: true)
let vc1 = self.storyboard?.instantiateViewController(withIdentifier: "CreateNewProjectViewController") as! CreateNewProjectViewController
let window = UIApplication.shared.windows.first
window?.rootViewController = vc1
tabBarController?.tabBar.isHidden = false
}else {
// let vc2 = storyboard?.instantiateViewController(identifier: "ProjectsViewController") as! ProjectsViewController
// self.navigationController?.pushViewController(vc2, animated: true)
let vc2 = self.storyboard?.instantiateViewController(withIdentifier: "ProjectsViewController") as! ProjectsViewController
let window = UIApplication.shared.windows.first
window?.rootViewController = vc2
tabBarController?.tabBar.isHidden = false
}
}
}
I tried with navigationController but it is showing blank screen.
If I try setting the view controller with "
let vc2 = self.storyboard?.instantiateViewController(withIdentifier: "ProjectsViewController") as! ProjectsViewController
let window = UIApplication.shared.windows.first
window?.rootViewController = vc2
tabBarController?.tabBar.isHidden = false
".
It does not show the tab bar, please help me get it correctly.
Thank you!
So I am having a problem loading a new view controller on the same tab bar item. I have a tab bar controller as my root controller and 4 tabs on the tab bar. One of which is the account tab, when the user is logged in he would need to see an overview of his account details but when there is no user there needs to be a sign-in / login page on that tab bar item. For the moment I have a custom tab bar class for my tabbarcontroller with a function that I thought was the solution but nothing happens. I have put a breakpoint in the view controller that needs to loaded (the user detail page) and it comes in the ViewDidLoad so it loads but it doesn't appear on the screen.
Hope I can finally solve this problem!
Kind regards
B.
this is my custom tab bar controller class:
import UIKit
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
var window: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
let index = tabBarController.viewControllers?.firstIndex(of: viewController)
if index == 3{ // Index of Account tab
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = mainStoryBoard.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
window?.rootViewController = loginVC
window?.makeKeyAndVisible()
}
return true// you decide
}
}
here is the code that presents full screen loginVc
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
let index = tabBarController.viewControllers?.firstIndex(of: viewController)
if index == 1{ // Index of Account tab
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = mainStoryBoard.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
loginVC.modalPresentationStyle = .fullScreen
self.present(loginVC, animated: false, completion: nil)
}
return true// you decide
}
If you also want to have bottom bar ... your that viewController should be Navigation Controller and you can push your desired ViewController then like
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
let index = tabBarController.viewControllers?.firstIndex(of: viewController)
if index == 1{ // Index of Account tab
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = mainStoryBoard.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
// loginVC.modalPresentationStyle = .fullScreen
if let navigation = viewController as? UINavigationController {
navigation.pushViewController(loginVC, animated: false)
}
}
return true// you decide
}
It will show back button and navigationBar on top
if you dont want NavigationBar then in your MessagesViewController did load function call
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
// Do any additional setup after loading the view.
}
And if you just want to hide back button then call
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.setHidesBackButton(true, animated: false);
// Do any additional setup after loading the view.
}
Changing your rootViewController is bad practice in Swift.
The correct way to handle tasks like this is to override your accountViewController's viewWillAppear function to determine if the user isn't logged in and present your loginViewController. Try something like:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// authManager would be a custom class that manages authorization with a property for loggedIn
if authManager.loggedIn == false {
guard let loginVC = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "login") as? LoginViewController else { return }
self.present(loginVC, animated: true, completion: nil)
}
}
I have UIContainerView inside which I have UINavigationController and other VC embedded in UINavigationController (7 of them).
So, it looks like this:
On that 7 screens inside UINavigationController I'm calling a popup screen which then should redirect to other screen, from which user can perform back segue to the VC popup was called from.
So, user journey inside UINavigationController will look like:
Opened VC1(2,3,5,6,7) -> Called popup VC -> Pressed button -> opened VC4 -> pressed navigation back button -> returned to VC1.
Here's my code:
#IBAction func didPressAuthors(_ sender: UIButton) {
self.dismiss(animated: true) {
if let navigation = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
let vc1 = self.storyboard!.instantiateViewController(withIdentifier: "FirstVC")
let vc2 = self.storyboard!.instantiateViewController(withIdentifier: "SecondVC")
let vc3 = self.storyboard!.instantiateViewController(withIdentifier: "ThirdVC")
let vc4 = self.storyboard!.instantiateViewController(withIdentifier: "FourthVC")
let vc5 = self.storyboard!.instantiateViewController(withIdentifier: "FifthVC")
let finalVC = self.storyboard!.instantiateViewController(withIdentifier: "Authors")
let userJourney = self.defaults.array(forKey: DefaultKeys.screenID.rawValue)
for vcName in userJourney! {
switch String(describing: vcName) {
case "FirstVC":
self.journeyVC.append(vc1)
case "SecondVC":
self.journeyVC.append(vc2)
case "ThirdVC":
self.journeyVC.append(vc3)
case "FourthVC":
self.journeyVC.append(vc4)
case "FifthVC":
self.journeyVC.append(vc5)
default:
self.journeyVC.append(finalVC)
}
}
self.journeyVC.append(finalVC)
navigation.setViewControllers(self.journeyVC, animated: true)
}
}
}
Problem:
When I didn't have UINavigationController inside UIContainerView this code worked just fine, but after I had added Container View - it stopped. Dismiss func is called, but nothing happens. What I'm missing? Should I change how VC is presented?
Would be grateful if anyone could help me fix this issue. Many thanks and have a nice weekend!
Your current root isn't the navigation here
if let navigation = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
it's a usual VC that the navigation is inserted as a child so try
if let root = UIApplication.shared.keyWindow?.rootViewController as? RootVC {
let nav = root.children![0] as! UINavigationController
I've asked a question before and it works successfully thanks to the one of the answers How to instantiate a navigation controller from another view controller?, but I have come across a new problem which is , whenever i click a button from a show detail segue, it supposed to navigate to a normal tab with a navigation bar, but it did nothing.
Here's the storyboard
Here's the scenario
1) User click on a button on a firstViewController and it will segue to a thirdViewController which is in show detail
2) User click another button which will then supposed to go to a secondViewController with the codes below
Here's the code
in ThirdViewController
#IBAction func buttonTapped(sender: UIButton) {
guard let tabBarController = tabBarController else { return }
let navController = tabBarController.viewControllers![1] as! UINavigationController
let secondViewController = navController.topViewController as! SecondViewController
secondViewController.name = "My name is TDog"
tabBarController.selectedIndex = 1
}
What did i do wrong? Do i still need to instantiate?
You have to dismiss the actual ThirdViewController but, in this class, you dont know yet UITabViewController so a way to obtain it ( not to re-instantiate but to recall from memory) is to call the rootViewController of your window (it can be done in your project):
#IBAction func buttonTapped(sender: AnyObject) {
self.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let tabBarController = appDelegate.window!.rootViewController
if tabBarController is UITabBarController {
let tab = tabBarController as! UITabBarController
let navController = tab.viewControllers![1] as! UINavigationController
let secondViewController = navController.topViewController as! SecondViewController
secondViewController.nameString = "My name is TDog"
tab.selectedIndex = 1
}
}
You got to call
self.presentingViewController.dismissViewControllerAnimated(true, completion: nil)
after the following line
tabBarController.selectedIndex = 1
My goal is whenever i click a button on a first view controller, then it will navigate to another controller which is a navigation controller.
firstViewController and secondViewController has no connection or anything.
Picture
I used this code
#IBAction func buttonTapped(sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("secondViewCtrl") as! SecondViewController
self.presentViewController(vc, animated: true, completion: nil)
}
The reason why i instatiate so that I could pass data like
vc.name = "Myname"
The problem with this code is that it doesn't present navigation bar and as well as the tab bar. What should I do to show both?
Updated question
#IBAction func buttonTapped(sender: UIButton) {
guard let tabBarController = tabBarController else { return }
tabBarController.selectedIndex = 1
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("trackYourGenie") as! TrackYourGenieViewController
let navController = tabBarController.viewControllers![1]
let secondViewController = navController.topViewController
vc.name = "Myname"
}
You are instantiating the view controller hence you wouldn't get the navigation bar. To get the navigation bar please instantiate navigation controller and since the second view is only child you would get the second view by default.
#IBAction func buttonTapped(sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("Navigation Controller Id") as! UINavigationController
self.presentViewController(vc, animated: true, completion: nil)
}
The above code should give you the navigation bar.
A safe approach:
guard let tabBarController = tabBarController else { return }
tabBarController.selectedIndex = 1
If you need to access to your tabBarController to pass datas you can do simply:
let navController = tabBarController.viewControllers[1]! as! UINavigationController
let secondViewController = navController.topViewController
Your method could be:
#IBAction func buttonTapped(sender: UIButton) {
guard let tabBarController = tabBarController else { return }
let navController = tabBarController.viewControllers[1]! as! UINavigationController
let secondViewController = navController.topViewController as! SecondViewController
secondViewController.name = "my name"
tabBarController.selectedIndex = 1
}
In your case, the following code should be sufficient :
self.tabBarController.selectedIndex = 1
As UITabBarController is your rootViewController, you can access it with self.tabBarController. You don't have to instantiate UINavigationController as it is in the storyboard.
I can see from your StoryBoard that you have a TabBarController. If your configuration is that and FirstViewController is on first tab and SecondViewController on second tab, you can just change TabBarController selectedIndex property:
#IBAction func buttonTapped(sender: UIButton) {
tabBarController?.selectedIndex = 1
}
If you want to pass data to SecondViewController you can try one of these solutions:
let controller = tabBarController.viewControllers[1] as SecondViewController!
controller.data = "some data"
This soultion could not work since SecondViewController is not ready yet.
Create a singleton class where to save data, then retrieve data in SecondViewController viewDidLoad method
Save data in UserDefaults, then retrieve data in SecondViewController viewDidLoad method (bad solution if information doesn't have to be persistent)
Extend UITabBarController and use it, create a custom var in tabBarController, put data in that variable and then retrieve data in SecondViewController viewDidLoad method
Then clear data if needed.
Navigation will not work from firstViewController as to navigate something we need UINavigationController.
Try Like This