Set Initial view controller with tab bar and nav bar - swift

I have a tab bar controller as my initial view controller in storyboard (with the arrow). However when a user signs in the first time, I'm trying to change the initial view controller programmatically in didFinishLaunchingWithOptions: to start out at a navigation controller to go through a couple VCs for them to enter signup information (I don't want a tab bar for this). Although I'm changing the initial VC in my app delegate, I'm getting a crash with Could not cast value of type 'UITabBarController' (0x1031374f8) to 'UINavigationController' (0x1031374a8). I was under the impression that the initial VC would be changed and not reference the Tab Bar Controller at all? Is there a way I can accomplish changing the initial VC without the Tab Bar Controller being referenced? Here is my code in didFinishLaunchingWithOptions:, any help is GREATLY appreciated!
if User.current() == nil {
let storyboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationControllerr:UINavigationController = storyboard.instantiateInitialViewController() as! UINavigationController
let initialViewController = storyboard.instantiateViewController(withIdentifier: "SplashPageVC")
navigationControllerr.viewControllers = [initialViewController]
self.window?.rootViewController = navigationControllerr
}

Why dont you try something like this.
if User.current() == nil {
let initialViewController = storyboard.instantiateViewController(withIdentifier: "SplashPageVC")
let navigationController = UINavigationController(rootViewController: initialViewController)
self.window?.rootViewController = navigationControllerr
}

Related

Instantiating deep View Controller with Tab bar and Navigation

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!

Swift 3+: Multiple Storyboards with TabBarController. Set TabBarController Tab, Segue to desired ViewController

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.

How to control multiple navigation controller in an iOS project

The structure of my project is as follows.
Initially when the user is registering or trying to login the initial navigation controller should work and after successfully registering / loggin in the user should be taken to first tab of tab bar controller. But the issue that i am facing is that i am getting 2 navigation bars in the tab bar view. Can someone guide me how to implement this in the correct way.
Thanks in advance
At some point, I would assume that the app is able to determine whether the user loggedin or not, based on that you have to set the desired root view controller for the app.
For such a case, the best place to do that is application(_:didFinishLaunchingWithOptions:) method in the AppDelegate file:
Tells the delegate that the launch process is almost done and the app
is almost ready to run.
For simplicity, let's say that you are saving isLoggedin boolean in the UserDefault, so it could be achieved like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// the flag for determining whether the user loggedin or not
let isLoggedin = UserDefaults.standard.bool(forKey: "K_isLoggedin")
// the desired initial view controller (based on the value of `isLoggedin`)
let initialViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: isLoggedin ? "TabbarIdentifier" : "FirstNavigationIdentifier")
// setting the app rootViewController
window?.rootViewController = initialViewController
return true
}
Note that "TabbarIdentifier" represents the tabbar controller at the storyboard and also "FirstNavigationIdentifier" represents the first navigation view controller at the storyboard.
if you are unaware of how to set the view controller identifier, checking this answer should help.
Technically speaking, setting the desired root view controller means setting the rootViewController to the main window of the app (AppDelegate window).
Make one of the two navigation controllers from tabbar to initial viewcontroller and add the following in Appdelegate's didFinishLAunching
if (isDashboardVC == nil || isDashboardVC == false)
{
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController:UINavigationController = mainStoryboardIpad.instantiateViewController(withIdentifier: "navigationmain") as! UINavigationController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
}
else
{
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarIntial : UITabBarController = mainStoryboardIpad.instantiateViewController(withIdentifier: "Tabbarcontroller") as! UITabBarController
let navigationController2:UINavigationController = mainStoryboardIpad.instantiateViewController(withIdentifier: "yourViewControllerName") as! UINavigationController
let navigationController:UINavigationController = mainStoryboardIpad.instantiateViewController(withIdentifier: "yourViewControllerName") as! UINavigationController
let navigationController3:UINavigationController = mainStoryboardIpad.instantiateViewController(withIdentifier: "yourViewControllerName") as! UINavigationController
tabBarIntial.viewControllers = [navigationController2, navigationController, navigationController3]
tabBarIntial.selectedIndex = 1
}

Attempt to present * whose view is not in the window hierarchy

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. :)

how to call existing navigationcontroller in Swift

I have 3 items in my storyboard. 1. viewController (A) connected to 2. Navigation controller and 3. viewController (B) is NOT connected to anything
All 3 items have restoration identifiers set in the storyboard.
viewController (B) is the initial view controller.
I am trying in B to get the navigation controller that A is attached to, but always returns nil:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewControllerWithIdentifier("viewControllerA") as! ViewControllerA
print(vc.navigationController?) // always prints nil
why!?
UPDATE#1
I can't declare a UINavigationController like a view controller. I've tried setting navigationcontroller with the id of 'myNavigationController' as storyboardID:
When storyboard, I get this error
let navigationController = storyBoard.instantiateViewControllerWithIdentifier("myNavigationController") as! UINavigationController
print(self.navigationController!) // fatal error: unexpectedly found nil while unwrapping an Optional value
I've also tried setting the id in restoration identifier, It bombed earlier at the instantiating line
#ColdLogic, see what hits I get when I search the entire project for that identifier:
Because you never instantiated the navigation controller, you instantiated view controller A. You would want something like this if you want both the nav controller and the view controller to be setup like they are in the storyboard
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyBoard.instantiateViewControllerWithIdentifier("YourNavControllerIdentifier") as! UINavigationController
let vc = navigationController.topViewController as! ViewControllerA
Your code directly instantiates an object of type ViewControllerA. Which, unless you setup logic to do it, does not have a navigation controller by default.
This Worked for me!
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyBoard.instantiateViewController(withIdentifier: "HomeNavigationContoller") as! UINavigationController
let viewController = storyBoard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
navigationController.pushViewController(messageVC, animated: true)
self.present(navigationController, animated: true, completion: nil)
Use NavigationController identifier to access the NavigationController and ViewController A is already attached to it, so it will automatically get loaded to NavigationController