I'm developing an iOS application with a tab bar and navigation. When I press a button in the first tab I want it to load the second tab with a navigation controller and push the next view. Can someone help with this?
You can programatically select tab by
self.tabBarController.selectedIndex=1;
The button in the first tab sends a message to the tab controller, telling it to select the second tab. Then you send a message to the rootView of the second tab, which is a navigation controller, and tell it to push such and such an object that you create in the class with the first button.
Set selectedViewController:
self.tabBarController.selectedViewController = viewControllerYouWant;
For example,
self.tabBarController.selectedViewController
= [self.tabBarController.viewControllers objectAtIndex:2];
Here is a Swift solution for anyone that needs it. I had to do the same thing just go from a nested view controller in tab 2 to another nested view controller in tab 4. Here is how I achieved that:
func goToHelpViewController(){
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let desiredIndex:Int = 3
self.tabBarController?.selectedIndex = desiredIndex
let settingsStoryBoard = UIStoryboard(name: "SettingsSection", bundle: nil)
let helpViewController = settingsStoryBoard.instantiateViewControllerWithIdentifier("HelpViewController") as! HelpViewController
let settingsRootNavigationController = self.tabBarController?.viewControllers![desiredIndex] as! UINavigationController
settingsRootNavigationController.popToRootViewControllerAnimated(false)
settingsRootNavigationController.pushViewController(helpViewController, animated: true)
})
}
Swift 3.0
func goToHelpViewController(){
DispatchQueue.main.async(execute: { () -> Void in
let desiredIndex:Int = 1
self.tabBarController?.selectedIndex = desiredIndex
let settingsStoryBoard = UIStoryboard(name: "SettingsSection", bundle: nil)
let helpViewController = settingsStoryBoard.instantiateViewController(withIdentifier: "HelpViewController") as! HelpViewController
let settingsRootNavigationController = self.tabBarController?.viewControllers![desiredIndex] as! UINavigationController
settingsRootNavigationController.popToRootViewController(animated: false)
settingsRootNavigationController.pushViewController(helpViewController, animated: true)
})
}
Related
I am using Xcode 11 and Swift 5.
Upon receiving an APNS notification, I need to jump to a view controller deep in my storyboard from AppDelegate. This viewController (chatVC) is behind a tabbar and a navigation controller and several other view controllers. See the image below. I know how to check the notification for tags and how to use launchOptions in AppDelegate to trigger the jump. But I am struggling with how to establish the context for that last view controller so that the user can use the back button all the way back to the tab bar controller.
I have read many SO answers, and tried many approaches but none seem to have the same embedding of a nav controller inside a tab bar controller. Here is my code in AppDelegate (after reading the tag in the notification):
if tag == "CS" {
// Set up a Chat View Controller.
if let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController,
let tabBarVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController) as? UITabBarController,
let csVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.csViewController) as? CustomerServiceViewController,
let helpVC = UIStoryboard(name:"Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.helpVC) as? HelpViewController
{
// Set the customer service document Id.
chatVC.cs = cs
// Make the tabBarVC the Root View Controller
self.window?.rootViewController = tabBarVC
self.window?.makeKeyAndVisible()
// Select the Favorites Index.
tabBarVC.selectedIndex = 0
// Push the Customer Service VC on top.
tabBarVC.show(csVC, sender: Any?.self)
// Push the Help VC on top of Customer Service VC.
csVC.show(helpVC, sender: Any?.self)
// Push the the chat detail page on top.
helpVC.show(chatVC, sender: Any?.self)
}
}
}
return true
What can I do to jump to the chatVC and set up the navigation context beneath it so the back button can be used?
Here is how you can do it:
guard let tabBarVC = UIApplication.shared.windows.filter( {$0.rootViewController is UITabBarController } ).first?.rootViewController as? UITabBarController else { return }
tabBarVC.selectedIndex = 0 //you can select another tab if needed
guard let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController else { return }
if let navController = tabBarVC.viewControllers?[0] as? UINavigationController {
navController.pushViewController(chatVC, animated: true)
}
You can also pass objects from your notification to your chatVC here before pushing it, in case you need to do that.
Hope this works for you!
Working in multiple storyboards. Getting from point A->TabBarController(tab 2)->NavigationControllerA->StoryboardReference->NavigationControllerB->ViewControllerA->ViewControllerB. No need for data passing.
My tab bar controller is set up in its own storyboard, with each tab going through a navigation controller to a storyboard reference. Then the storyboard reference links to another storyboard (obv.), and through a navigation controller to ViewControllerA. I then want to go to performSegue to ViewControllerB. This is all done from a UIViewController Extension.
From what I've been reading, I should approach like this:
let tab = 2
let sbOne = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = sbOne.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
tabBarController.selectedIndex = tab
let sbTwo = UIStoryboard(name: "sbTwo", bundle: nil)
let viewControllerA = sbTwo.instantiateViewController(withIdentifier: "ViewControllerA")
performSegue(withIdentifier: "toViewControllerB", sender: self)
I receive NSInvalidArgument: Point A "has no segue with identifier 'toViewControllerB'"
Swift 3+
I’d be surprised if this is the best answer but this is the only solution that worked. A bit of my problem stemmed from lack of understanding. But the concept I arrived at was to separate the functions into two parts. Setting the tabbarcontroller tab, then segueing to the destination using a variable struct.
I ended up setting a struct at Point A.
struct Destination {
static var currentID = Int()
}
And on button press I would set the variable with this and then and call the function below:
Destination.currentID = currentID
goToViewControllerA()
Then using part of the code I had before I could set the tabbar Controller as the root view controller and go to ViewControllerA (the first view controller) on the selected tab. This is done in an uiviewcontroller extension but can also be done in a function in Point A.
goToViewControllerA(){
let tab = 2
let sbOne = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = sbOne.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
tabBarController.selectedIndex = tab
}
It’s then just a matter of checking if the struct static var is set, and segueing to the destination controller (ViewControllerB). This is done in ViewControllerA, or if you have to segue through multiple view controllers you could model your function on each viewcontroller using this in the viewdidload.
If Destination.currentID != Int() {
performSegue(withIdentifier:“toViewControllerBSegue”, sender:self)
}
I’d love to learn a better way of doing this if it exists.
I have a case in one of my Swift files which runs the following method:
case .editProfile:
vc = PoiDetailViewController()
self.navigationController?.popViewController(vc,animated: true)
It doesn't seem to work when I run the code.
The view controller is located on the main storyboard and I would like PoiDetailViewController to display when this case is called.
If I understand it correctly, you should be able to achieve what you're looking for by present in your code. Like so:
case .editProfile:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "PoiDetailViewController") // or whatever identifier you have given it
self.present(vc, animated: true, completion: nil)
That should work, however, make sure you set a storyboard ID for your view controller in the identity inspector.
No segues are required.
To set up a storyboard ID click on the View Controller you would like to use (in this case, PoiDetailViewController) and click the identity inspector icon and set a storyboard ID where it asks. I have attached an image so you can see where it needs to go (in the field marked 'Storyboard ID')
Hope that helps.
As per my Understanding you need to pop to specific View Controller if it is in stack and if not then you need to push to that controller
var loginVCFound:Bool = false;
let navigationController : UINavigationController! = self.window!.rootViewController as! UINavigationController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil);
let viewControllers: [UIViewController] = navigationController.viewControllers
for aViewController in viewControllers {
if aViewController is LoginVC {// Pass the name of your controller here
loginVCFound = true;
navigationController.popToViewController(aViewController, animated: true)
break;
}
}
if !loginVCFound { // change the identifier and VC with yours
let objLoginVC = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
navigationController.pushViewController(objLoginVC, animated: true)
}
I have a UITabBarController with 3 tabs and a SettingsVC where you can choose the appearance of MainVC (There are 2 different ViewControllers which based on what user prefers should be shown as the MainVC).
I made the application and it works but it's really buggy, cause I was just pushing the ViewControllers on top of each other, and in some certain conditions you see bugs while switching tabs.
Main.storyboard
Here is some part of my code(after trying a lot of stuff) which changes the VC's based on UserDefaults
CheckSettingsStatus.swift
//
// Check for Double Column Settings and navigate to Double Column VC
//
func checkMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 1{
//let appDel = UIApplication.shared.delegate as! AppDelegate
//appDel.goToDoubleMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "DoubleColumnMainVC") as! DoubleColumnMainVC
self.navigationController?.viewControllers[0] = mainVC
//navigationController?.pushViewController(mainVC, animated: false)
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
self.navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
//
// same as function above but goes to MainVC
//
func checkDoubleColumnMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 0{
// let appDel = UIApplication.shared.delegate as! AppDelegate
// appDel.goToMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "MainVC") as! MainVC
self.navigationController?.viewControllers[0] = mainVC
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
With this in ViewWillAppear of both this Controllers I call these functions to switch accordingly.
Any ideas what I'm doing wrong? Can I push controllers from UITabBarController and not NavigationController? something like:
tabBarController.pushViewController(mainVC, animated: false)
instead of:
navigationController.pushViewController(mainVC, animated: false)
Thank you in advance
UPDATE:
In my best case when everything works nicely, on first launch of the app (to the second mainVC) the Buttons in NavBar doesn't work.
EDIT:
I just realised that the NavigationBar that I'm seeing above my SecondMainVC is the NavigationBar from MainVC, that is why the buttons are not working. how is that possible?
I faced same issue while trying to develop a tab bar controller in storyboard , for this reason I found that the best way is to implement it by code.
so you can make your code look like this :
1) create a a sub class of UITabController and let it conform to UITabBarControllerDelegate
class TabBarVC: UITabBarController , UITabBarControllerDelegate {
2)then write this method in your class to initialize the tabs in your project:
private func settingUpTabBarAndInitializingViewControllers(){
//self delagting
self.delegate = self
//home tab
let homeStoryboard = UIStoryboard(name: Constants.StoryBoards.homeStoryboard, bundle: nil)
let homeVC = homeStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.homeScreen) as! HomeVC
// shop tab
let shopStoybaord = UIStoryboard(name: Constants.StoryBoards.shopStoryboard, bundle: nil)
let shopVC = shopStoybaord.instantiateViewController(withIdentifier: Constants.ControllersIDs.shopScreen) as! ShopVC
//offers tab
let offersStoryboard = UIStoryboard(name: Constants.StoryBoards.offersStoryboard, bundle: nil)
let offersVC = offersStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.offersScreen) as! OffersVC
//more tab
let moreStoryboard = UIStoryboard(name: Constants.StoryBoards.MoreScreenStoryboard, bundle: nil)
let moreVC = moreStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.moreVCScreen) as! MoreOptionsTVC
//setting VCs
self.viewControllers = [homeVC , shopVC , offersVC , moreVC]
//setting buttons
//buttons images
let homeImage = UIImage(named: "TabHome")
let shopImage = UIImage(named : "TabShop")
let offerImage = UIImage(named: "TabOffers")
let moreImage = UIImage(named: "TabMenu")
// buttons shakhsiyan
let homeButton = UITabBarItem(title: homeText, image: homeImage, selectedImage: homeTappedImage)
let shopButton = UITabBarItem(title: shopText, image: shopImage, selectedImage: shopTappedImage)
let offersButton = UITabBarItem(title: offersText, image: offerImage, selectedImage: offersTappedImage)
let moreButton = UITabBarItem(title: moreText, image: moreImage, selectedImage: moreTappedImage)
homeVC.tabBarItem = homeButton
shopVC.tabBarItem = shopButton
offersVC.tabBarItem = offersButton
moreVC.tabBarItem = moreButton
}
P.S. you can add tabs as much as you want and while scrolling in them you wont face any bug,
make sure to have only one navigation controller before your tab bar controller.
Check this hope it helps
Ok you can create a DEMO to see how thing works.After that you can implement it in your project.I know it may not look good to you but read it once and see image attached i am sure will get your answer.
1.I have created two UIViewControllers with the UITabBarController.
2.I have created class ItemFirstVC,ItemTwoVC and assigned to UIViewControllers in storyboard
3.Each controller is embedded in UINavigationController
4.Create a custom class of UITabBarController
5.Assign it to UITabBarController in storyboard
6.Now Each UIViewController has a button named as Firs Button,Second Button
7.From each button i have connected Action Segue to one more controller as you can see in image.
8.Now i have created a third UIViewController which is separate from UITabBarController.
9.Now we will load that Third UIViewController in UITabBarController
10.Create one more class ItemThirdVC and assign it to third UIViewController.
11.It also has a button named as third button action segued to one more UIViewController
12.Now switch to custom class of UITabBarContoller
// variable required to see work in action
var count = 0
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// remember to assign delegate
delegate = self
}
// this delegate indicates whether this tab should be selected or not
extension CustomTabBarController : UITabBarControllerDelegate{
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// switch to UIViewController tabs multiple times
count = count + 1
if(count == 5){
// when count reaches 5 a new controller will load
let navigationController = viewController as? UINavigationController
guard navigationController != nil else {return true}
let viewController = storyboard?.instantiateViewController(withIdentifier: "ItemThreeVC")
guard let vc = viewController else {return true}
navigationController?.setViewControllers([vc], animated: false)
}
return true
}
}
I am using the following code in AppDelegate to display a popup window within the app when a button is selected, i will eventually move this to a label hyperlink but just testing currently.
let storyboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("test") as! ViewController
let popOverVC = UIStoryboard(name:"Main", bundle: nil).instantiateViewControllerWithIdentifier("sbPopUpID") as! PopUpViewController
storyboard.addChildViewController(popOverVC)
popOverVC.view.frame = storyboard.view.frame
storyboard.view.addSubview(popOverVC.view)
popOverVC.didMoveToParentViewController(storyboard)
self.window?.rootViewController?.presentViewController(storyboard, animated: true, completion: nil)
The first time i select a button this works correctly, however on all subsequent button presses the following error is displayed.
2016-10-28 11:27:40.551 testfordeeplinks[20496:104536] Warning:
Attempt to present
on whose view is not
in the window hierarchy!
For anyone who stumbles across this in the future, this is how i resolved;
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = storyboard
rootViewController doesn't necessarily refer to the view controller that is currently visible. For example, if you utilise a UINavigationController, rootViewController will hold a reference to that navigation controller which in itself is buried deep in the view hierarchy and hence when you try to present a view controller on it get an error. What I would recommend doing is grabbing the last view controller in navController.viewControllers and presenting your popup on that. See the following code:
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
if let navController = appDel.keyWindow?.rootViewController as? UINavigationController {
if let visibleVC = navController.viewControllers.last {
visibleVC.presentViewController(storyboard, animated: true, completion: nil)
}
}
Give that a try and let me know. :)