self.navigationController is always nil - ios

UI flow:
AppDelegate
-->
LoginViewController (not in the storyboard)
-->
navigation controller (in the storyboard)
-->
PFQueryTableViewController (in the storyboard) named "OrdersVC"
This is the navigation controller with OrdersVC:
This is my AppDelegate:
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// ...
// initial VC
let VC = LoginViewController()
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window!.rootViewController = VC
window!.makeKeyAndVisible()
return true
}
The above works fine. Then, from LoginViewController, I am then trying to display my storyboard's initial VC which is a navigation controller hosting a PFQueryTableViewController. Note that LoginViewController is not in the storyboard.
let destVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("OrdersVC") as! UITableViewController
// will throw "unexpectedly found nil"
let navController = UINavigationController(rootViewController: destVC)
navController.pushViewController(destVC, animated: true)
// will throw "unexpectedly found nil"
self.presentViewController(navController, animated: true, completion: nil)
Problem is that in my PFQueryTableViewController's viewDidLoad and viewDidAppear the following statement is always nil:
// navitaionController is nil
self.navigationController!.navigationBar.translucent = false
So how can I correctly instantiate PFQueryTableViewController inside its navigation controller?

You are instantiating the OrdersVC instead of instantiating the navigation controller into which it is embedded and which is the "initial" view controller of your storyboard. Use instantiateInitialViewController instead of using the identifier.
let nav = storyboard.instantiateInitialViewController()
self.window!.rootViewController = nav
The reason for the confusion is that you are "unlinking" the initial view controller from the storyboard with your login controller. You have to add the initial view controller back to the main window.

Related

Swift: Presenting a viewController over UITabBarController

After my app is finished launching, I want my app to open a LoginViewController if a current user is not logged in. Otherwise, it goes to a ViewController which is at the index of 0 (default value). So if not logged in, then show a modal view over UITabBarController.
My initial viewcontroller is a UITabBarController, which is created in UIStoryboard. It is check-marked as Initial View and is connected to other five viewcontrollers graphically.
Actually, I think I know the reason why a LoginViewController does not appear. It is because the value result was nil (I checked in the console). But why?? Other than this behavior, everything is working well. UITabBarController seems to be working without any problem.
My LoginViewController is embedded with UINavigationViewController. This code is inside a ViewController which is at a selected index of 0. It is put inside viewDidAppear() method
let main = UIStoryboard(name: "Main", bundle: nil)
let view = main.instantiateViewController(withIdentifier: "login") as! LoginViewController
let nav = UINavigationController(rootViewController: view)
self.tabBarController?.present(nav, animated: false, completion: nil)
However, it is not working.
Change the rootViewController of window to UINavigationController if user is not logged in and if he is change it to the tabBarController
You should implement the logic of deciding what is the desired initial view controller to be the root (based on whether the user logged in or not) before navigating to any view controller, application(_:didFinishLaunchingWithOptions:) method would be appropriate for such an implementation:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// let's assume that you are recognizing if the user logged in by a flag called 'isLoggedin':
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if isLoggedin { // show display main view controller
let mainViewController = storyboard.instantiateViewController(withIdentifier: "mainViewController")
// setup any needed config for mainViewController...
self.window?.rootViewController = mainViewController
} else { // display login view controller
let loginViewController = storyboard.instantiateViewController(withIdentifier: "loginViewController")
// setup any needed config for loginViewController...
self.window?.rootViewController = loginViewController
}
self.window?.makeKeyAndVisible()
return true
}
Or as a shorter version:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let isLoggedin = false
let initialViewController = storyboard.instantiateViewController(withIdentifier: isLoggedin ? "mainViewController" : "loginViewController")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
At this point, after the app finished launching (without jumping to any view controller yet), the initial view controller would be displayed based on the user logging in validation. I would assume that it would be a better behavior unless there is a specific requirement to present the login view controller on the of the tabbar controller; Logically, there is no even need to navigate to the tabbar controller if the user didn't login yet.
I run your code, Here is issue:
2018-01-18 16:23:52.845273+0800 try[8180:315334]
Warning: Attempt to present on whose view is not in the window hierarchy!
self.tabBarController?.present(nav, animated: false, completion: nil)
this line is very wired. I seldom saw.
So you can also let a child controller of self.tabBarController to present nav.
The choose one of the tab bar.

Navigation Bar Disappears Change Root ViewController and then reassign

I've made the launch screen the root view while my app makes a request to firebase and then reassign the root view once the request has been completed. Unfortunately when I do this my navigation bar disappears or is covered up by the new root view. When I run the simulator I can see the navigation bar briefly and then it get covered up by my TableViewController. How do I keep this from happening?
Here is my code from the AppDelegate where I make all of this happen:
var window: UIWindow?
let searchManager = SearchManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
makeRootViewLaunchScreen()
FirebaseApp.configure()
searchManager.getMosaicTitles { results in
self.searchManager.listOfMosaics = results
self.stopDisplayingLaunchScreen()
}
// Adds border to bottom of the nav bar
UINavigationBar.appearance().shadowImage = UIImage.imageWithColor(color: UIColor(red:0.00, green:0.87, blue:0.39, alpha:1.0))
// Override point for customization after application launch.
return true
}
func makeRootViewLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "launchScreen")
UIApplication.shared.keyWindow?.rootViewController = viewController
}
func stopDisplayingLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? SearchResultsTableViewController
viewController?.searchManager = searchManager
UIApplication.shared.keyWindow?.rootViewController = viewController
UIApplication.shared.keyWindow?.rootViewController?.navigationController?.isNavigationBarHidden = false
}
As you can see I tried to use UIApplication.shared.keyWindow?.rootViewController?.navigationController?.isNavigationBarHidden = false to force the navigation bar to appear but it doesn't. My app still looks like this:
You are setting a UIViewController as your Root controller. What you want to do is set a UINavigationController as your Root controller.
Either create a new navigation controller in your storyboard and load that one instead of loading "centralViewController", or modify your function like this:
func stopDisplayingLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? SearchResultsTableViewController {
viewController.searchManager = searchManager
// create a new UINavigationController
let newNavVC = UINavigationController()
// set the "root" VC of the NavVC to your SearchResultsTableViewController
newNavVC.setViewControllers([viewController], animated: false)
// use the new NavVC as the new rootViewController
UIApplication.shared.keyWindow?.rootViewController = newNavVC
UIApplication.shared.keyWindow?.rootViewController?.navigationController?.isNavigationBarHidden = false
}
}
Note: not tested, but this should get you on your way.
Edit: Another approach...
Instead of swapping the root controller of the key window, create a ViewController that indicates you are initializing / retrieving your data. That VC can be the "root" of a Navigation Controller.
Put your searchManager.getMosaicTitles function in that VC. When it has finished, replace the current "launch" controller in your Nav Controller with your TableVC. It will look something like this:
// note: searchManager will have to be a reference back to AppDelegate.searchManager
searchManager.getMosaicTitles { results in
self.searchManager.listOfMosaics = results
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? SearchResultsTableViewController {
viewController.searchManager = searchManager
// replace the current Nav VC stack with the new SearchResultsTableViewController
self.navigationController.setViewControllers([viewController], animated: false)
}
}
To prevent the lag between when the navigation bar controller appears and the tableView appears check out the following. Prevent navigation bar from disappearing without lag

Tabbar won't hide when pushed into a ViewController inside a UITabBarController

For the purpose of this question, I'm showing a stripped down version of my view hierarchy. My app contains a UITabBarController as the base. Each tab's top most view controller is a navigation controller and it has view controllers embedded in each of them.
Let's take the first tab.
UITabBarController -> UINavigationController -> UITableViewController -> UIViewController
Let's say the UITableViewController instance is some sort of a list and the UIViewController is the detail view. When the user taps on an item from the list, it takes you to the detail view. And when that happens I have set the UIViewController's hidesBottomBarWhenPushed property to true so that the tabbar at the bottom would hide when the user is in the detail view.
My app receives push notifications. When tapped on them, it should open directly into the detail view. I can get it to navigate there. But the issue is the tabbar at the bottom is still visible!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
window = UIWindow(frame: UIScreen.main.bounds)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "TabBarController") as! TabBarController
if openingFromPush {
let firstNavigationController = storyboard.instantiateViewController(withIdentifier: "FirstNavigationController") as! UINavigationController
let tableViewController = storyboard.instantiateViewController(withIdentifier: "TableViewController") as! TableViewController
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.hidesBottomBarWhenPushed = true
firstNavigationController.viewControllers = [tableViewController, viewController]
tabBarController.viewControllers?[0] = firstNavigationController
// tabBarController.tabBar.isHidden = true
window?.rootViewController = tabBarController
} else {
window?.rootViewController = tabBarController
}
window?.makeKeyAndVisible()
return true
}
I set that same hidesBottomBarWhenPushed property to true in the when I instantiate the view controller but that doesn't seem to have any effect. I even tried straight up hiding the tabbar like this tabBarController.tabBar.isHidden = true but that doesn't do anything at all either.
I can't figure how how to resolve this. Any help would be appreciated.
I attached a sample Xcode project here as well if that helps.
You can use this code for pushing detail view controller:
if openingFromPush {
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.hidesBottomBarWhenPushed = true
if let nvc = tabBarController.viewControllers?[0] as? UINavigationController {
nvc.pushViewController(viewController, animated: false)
}
window?.rootViewController = tabBarController
}
You don't need to init navigation view controller and table view controller again its already inside tab bar controller

Error trying to programmatically add a controller file to a navigation controller without creating storyboard

I am trying to push a view controller onto a navigation controller without designing a storyboard. Is this possible as I am new to Swift? I created my navigation controller in appdelegate.swift file:
let viewController: UIViewController = ViewController(nibName: nil, bundle: nil)
viewController.view.backgroundColor = UIColor.white
let navController: UINavigationController = UINavigationController(rootViewController: viewController)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.backgroundColor = UIColor.darkGray
self.window?.rootViewController = navController
self.window!.makeKeyAndVisible()
And now in ViewController.swift file, when the user clicks the button below is the file when I try to add the signInController:
func SignIn(sender: UIButton!) {
print("Im, here")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "signInController") as! signInController
self.navigationController?.pushViewController(controller, animated: true)
}
Below is my Error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Storyboard (<UIStoryboard: 0x608000269600>) doesn't contain a view controller with identifier 'signInController''
Question:
Do I have to create a storyboard and go to the inspector Id to add the storyboard Id? But what if I wanted to accomplish this without creating a storyboard file?
If you are not using a storyboard then you shouldn't attempt to create your sign-in view controller from a storyboard. Change the code to create the view controller directly:
func SignIn(sender: UIButton!) {
print("I'm, here")
let controller = signInController()
self.navigationController?.pushViewController(controller, animated: true)
}
BTW - you need to rename everything to follow standard naming conventions. Classnames should start with uppercase letters. All method, variable, and parameters names should start with lowercase letters.
Well you want to create a viewController without using the storyboard and you're still trying to use the storyboard in your code.
Try something like this instead:
var window: UIWindow?
var navigationController: UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.main.bounds)
// Override point for customization after application launch.
self.window!.backgroundColor = UIColor.white
self.window!.makeKeyAndVisible()
let myViewController: YourNewViewController = YourNewViewController()
self.navigationController = UINavigationController(rootViewController: myViewController!)
self.window!.rootViewController = self.navigationController
return true
}

Navigating between two viewController

I have set my initial ViewController as rootViewController in appDelegate because I don't use storyboard. It looks like this way:
var window: UIWindow?
var mainNavigationController: UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.mainNavigationController = UINavigationController()
var mainController: UIViewController? = TineLineViewController()
self.mainNavigationController!.pushViewController(mainController!, animated: true)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.rootViewController = mainController
self.window!.makeKeyAndVisible()
...
...
My application running and my TineLineViewController shows up.
I have in this class a UIButton which call this method:
func postLeft(_sender: AnyObject?)
{
println("go to secound view..")
let secondViewController = PostCreateController()
let appDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
self.navigationController?.presentViewController(secondViewController, animated: true, completion: nil)
appDelegate.window?.rootViewController = secondViewController
}
This way if I push the button the screen change and my secondViewController shows up, without animation...
If I try to change the view this way:
self.navigationController?.pushViewController(secondViewController, animated: true)
It's still don't have any animations and after the secoundViewController shows up my application crash with this message:
* Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'adding a root view controller as a child of view controller:'
* First throw call stack:
(
I don't know if is it a best way to set my rootviewController class in appDelagate and why can't navigate without adding this line to my potLeft function:
appDelegate.window?.rootViewController = secondViewController
Without this line I can see in my app consol, the secondViewController viewDidLoad method is called, but the controller not shows up, and I get this message to the cosole:
Warning: Attempt to present on whose view is not in the window hierarchy!
How to navigate between two view without use storyboard?
1) Set mainNavigationController to rootViewController
2) Use self.navigationController?.pushViewController(secondViewController, animated: true)
Explanation
Logically your root view controller is UINavigationController while you are setting TineLineViewController to app's delegate rootViewController property. That is why you're getting exception.
self.mainNavigationController = UINavigationController()
var mainController: UIViewController? = TineLineViewController()
self.mainNavigationController!.pushViewController(mainController!, animated: true)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
// ERROR is here
// self.window!.rootViewController = mainController
// your root view controller should be navigation controller
self.window!.rootViewController = mainNavigationController
self.window!.makeKeyAndVisible()
You can set the secondViewController as rootViewController of the navigationController with animation:
func postLeft(_sender: AnyObject?)
{
println("go to secound view..")
let secondViewController = PostCreateController()
self.navigationController?.setViewControllers([secondViewController], animated: true)
}
Hope this have helped you !

Resources