When should I set rootViewController? it's very confused - ios

Hi I am struggling with rootViewController I left some code fragment with an explanation below, please let me know.
If I do it like below, it works and every things fine.
private func presentLogin() {
log.info("presenting login..")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let vc = storyboard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
vc.modalPresentationStyle = .fullScreen
appDelegate.window!.rootViewController = vc
present(vc, animated: false)
}
right after that if I execute the code below, ios shows nothing but white blank page..
private func presentMain() {
log.info("presenting main..")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let vc = storyboard.instantiateViewController(withIdentifier: "MainTabBarController") as! MainTabBarController
vc.modalPresentationStyle = .fullScreen
appDelegate.window!.rootViewController = vc
present(vc, animated: false)
}
but when I deleted the code
appDelegate.window!.rootViewController = vc
everything is fine.
In other words, the code below works only the first time.
appDelegate.window!.rootViewController = vc
why? what am I missing?, I don't understand..

It seems there are a lot of bugs concerning the exchange of the root view controller. Switching the root view controller is also a little uncommon way of "navigation". I would recommend a different approach:
Upon app startup, use a "launch" view controller. This is your root view controllers, and it stays to be your root view controller all the time
if login is required, present a login view controller
after successful login, dismiss the login view and go on
if logged in, present the main application's entry view

Related

ios swift cannot dismiss view controller

I am opening new viewcontroller like
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let view_controller1 = storyBoard.instantiateViewController(withIdentifier: "incident_view_id") as! IncidentViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//self.present(registerViewController, animated: true)
let navigationController = UINavigationController.init(rootViewController: view_controller1)
appDelegate.window?.rootViewController = navigationController
Which works fine,
Now when I press a button I need to close current view and go back previous view, I tired
self.navigationController?.popToRootViewController(animated: true)
But not works, I tried this too
self.view.window!.rootViewController?.dismiss(animated: true, completion: nil)
But same result.
You are presenting your ViewController in a wrong way. The following line tells the application to no matter what ViewControllers you have opened, just throw it out and assign the UINavigationController what you are creating, as the rootViewController. So the reason your code, the dismiss, is not working because there are no other ViewControllers behind your current one.
appDelegate.window?.rootViewController = navigationController // Wrong
This usually goes into the beginning of the applications, where you define with which UIViewController you want to start your application. I believe you are calling the first code chunk you posted from a UIViewController. So what you need to do instead of creating a new UINavigationController and assigning it to the rootViewController, you just use the UIViewControllers navigation controller to push the next UIViewController.
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let view_controller1 = storyBoard.instantiateViewController(withIdentifier: "incident_view_id") as! IncidentViewController
navigationController?.pushViewController(view_controller1, animated: true)

iOS Swift: Pushing two view controllers when app launched from push notification

When a user launches the app from a push notification I present one view controller, then push another. My code to present the first VC is as follows
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeNav: UIViewController = mainStoryBoard.instantiateViewController(withIdentifier: "HomeNavController") as! UINavigationController
let homePageTableVC = mainStoryBoard.instantiateViewController(withIdentifier: String(describing: HomePageTableViewController.self)) as! HomePageTableViewController
homePageTableVC.tipToPresent = tipDay
homeNav.addChildViewController(homePageTableVC)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = homeNav
self.window?.makeKeyAndVisible()
The tipToPresent property is used by the homePageTableVC (in the viewDidLoad method) to then present the second VC, using this code:
if let tipDayToPresent = tipToPresent {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tipVC = storyboard.instantiateViewController(withIdentifier: String(describing: TipViewController.self)) as! TipViewController
tipVC.dayOfTip = tipDayToPresent
tipToPresent = nil
navigationController?.pushViewController(tipVC, animated: true)
}
This works well, but when I press the back button to return to the homePageTableVC, the navigation bar is blank. The title image, menu button, and right bar button that normally show up are not visible.
Any help is appreciated.
Good if solution by #augie works. I would suggest you should not change navigation stack when you handle push notification deep link. It should behave same as it does in normal app launch. By that way you don't need to handle any edge case and no need to set up different window.
Solution: Whenever someone click on push notification dismiss all presented controller and popToRootViewController and then navigate to desired screen.
Can you try changing this
homeNav.addChildViewController(homePageTableVC)
to this
homeNav.setViewControllers([homePageTableVC], animated: false)
I believe the problem is how you are adding your table on the navigation controller. Instead of making it your navigation's root viewcontroller you are adding it as a child vc.
Changing your code like this should work:
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
// Remove type UIViewController
let homeNav = mainStoryBoard.instantiateViewController(withIdentifier: "HomeNavController") as! UINavigationController
let homePageTableVC = mainStoryBoard.instantiateViewController(withIdentifier: String(describing: HomePageTableViewController.self)) as! HomePageTableViewController
homePageTableVC.tipToPresent = tipDay
// Set controllers instead of adding child
homeNav.setViewControllers([homePageTableVC], animated: false)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = homeNav
self.window?.makeKeyAndVisible()

Swipe Back Gesture After Login in Swift

In my application, after the user is successfully logged in via his email and password in the first storyboard (Auth.storyboard), he is directed to one of the ViewControllers in the second storyboard (Main.storyboard). The problem is that user is able to swipe back to the login screen in Auth.storyboard.
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
I know that with the above code it is possible to disable this swipe back gesture but according to most people it is not recommended.
Therefore I wonder that is there a better solution to prevent swipe back gesture after a user logged in.
The best way is to present your login controller modally, then dismiss it to don't add this controller in your navigation stack.
If it's your initial controller, embed it in a different controller than the next one or remove it from navigation stack programmatically.
You have to remove the Login ViewController from the stack and you can also make your home view controller as a rootViewController:
var mainNavigationController:UINavigationController?
//After login success
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeInstance = mainStoryboard.instantiateViewControllerWithIdentifier("HomeVC")
mainNavigationController = UINavigationController(rootViewController: homeInstance)
mainNavigationController?.navigationBar.hidden = true
self.window?.rootViewController = mainNavigationController
//Appdelegate code didFinishLaunching
if getUserDefault("isUser") == "YES" {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeInstance = mainStoryboard.instantiateViewControllerWithIdentifier("HomeVC")
mainNavigationController = UINavigationController(rootViewController: homeInstance)
mainNavigationController?.navigationBar.hidden = true
self.window?.rootViewController = mainNavigationController
}
else {
mainNavigationController = window!.rootViewController as? UINavigationController
}
When the login button is clicked you must have pushed the view controller to another screen.
Try to set the view controller instead of push:
func Login(){
let control = storyboard!.instantiateViewController(withIdentifier: identifier)
navigationController?.setViewControllers([control], animated: true)
}
If you were pushing your new controller ,then instead of pushing try adding that to keywindow like this . You will not be redirected to previous window then.
let homeStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let vc = homeStoryBoard.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let window = appDelegate.window {
window.rootViewController = vc
window.makeKeyAndVisible()
}

Load all views related to tab bar controller by hard code

I would like to jump from a viewController to the first viewController related to Tab Bar Controller through code.
The tabBarController Scene has storyboard id tabView.
I'm working on this way:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UITabBarController!
storyboard.instantiateViewController(withIdentifier: "tabView")
vc=storyboard.instantiateViewController(withIdentifier: "tabView") as! UITabBarController
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.present(vc as! UIViewController, animated: true, completion: nil)
}
But it loads only the first viewController (out of 5) without the tab bar related to. How can I solve it?
Oh, this code looks so wrong.
In your storyboard give and "tabView" ID to the TabBarController, not the ViewController inside it.
Why you are double instantiating ViewController? just do it once and assign it to the vc variable.
Why you've created delay before presenting the VC? It's some sort of workaround of something?
Working code:
let vc = storyboard.instantiateViewController(withIdentifier: "tabView") as! UITabBarController
self.present(vc, animated: true)
Use
(1)if you want to navigate from Appdelegate
let appDelegate = UIApplication.sharedApplication.delegate as! AppDelegate
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let tabBar = mainStoryboard.instantiateViewControllerWithIdentifier("TabBarController") as! TabBarController
appDelegate.window?.rootViewController = tabBar
appDelegate.window?.makeKeyAndVisible()
(2)if you want to navigate from viewcontroller which has root of navigation
self.navigationController?.pushViewController(tabBar, animated: true)

Swift programmatically navigate to another view controller/scene

I'm using following code to programmatically navigate to another ViewController. It works fine, but it some how hides the navigation bar. How do I fix this? (the navigation bar is created by embeding the ViewController in the navigation controller if that matters.)
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("nextView") as NextViewController
self.presentViewController(nextViewController, animated:true, completion:nil)
Swift 5
The default modal presentation style is a card. This shows the previous view controller at the top and allows the user to swipe away the presented view controller.
To retain the old style you need to modify the view controller you will be presenting like this:
newViewController.modalPresentationStyle = .fullScreen
This is the same for both programmatically created and storyboard created controllers.
Swift 3
With a programmatically created Controller
If you want to navigate to Controller created Programmatically, then do this:
let newViewController = NewViewController()
self.navigationController?.pushViewController(newViewController, animated: true)
With a StoryBoard created Controller
If you want to navigate to Controller on StoryBoard with Identifier "newViewController", then do this:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
self.present(newViewController, animated: true, completion: nil)
SWIFT 4.x
The Strings in double quotes always confuse me, so I think answer to this question needs some graphical presentation to clear this out.
For a banking app, I have a LoginViewController and a BalanceViewController. Each have their respective screens.
The app starts and shows the Login screen. When login is successful, app opens the Balance screen.
Here is how it looks:
The login success is handled like this:
let storyBoard: UIStoryboard = UIStoryboard(name: "Balance", bundle: nil)
let balanceViewController = storyBoard.instantiateViewController(withIdentifier: "balance") as! BalanceViewController
self.present(balanceViewController, animated: true, completion: nil)
As you can see, the storyboard ID 'balance' in small letters is what goes in the second line of the code, and this is the ID which is defined in the storyboard settings, as in the attached screenshot.
The term 'Balance' with capital 'B' is the name of the storyboard file, which is used in the first line of the code.
We know that using hard coded Strings in code is a very bad practice, but somehow in iOS development it has become a common practice, and Xcode doesn't even warn about them.
You should push the new viewcontroller by using current navigation controller, not present.
self.navigationController.pushViewController(nextViewController, animated: true)
According to #jaiswal Rajan in his answer. You can do a pushViewController like this:
let storyBoard: UIStoryboard = UIStoryboard(name: "NewBotStoryboard", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "NewViewController") as! NewViewController
self.navigationController?.pushViewController(newViewController, animated: true)
So If you present a view controller it will not show in navigation controller. It will just take complete screen. For this case you have to create another navigation controller and add your nextViewController as root for this and present this new navigationController.
Another way is to just push the view controller.
self.presentViewController(nextViewController, animated:true, completion:nil)
For more info check Apple documentation:-
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/doc/uid/TP40006926-CH3-SW96
OperationQueue.main.addOperation {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "Storyboard ID") as! NewViewController
self.present(newViewController, animated: true, completion: nil)
}
It worked for me when I put the code inside of the OperationQueue.main.addOperation, that will execute in the main thread for me.
All other answers sounds good, I would like to cover my case, where I had to make an animated LaunchScreen, then after 3 to 4 seconds of animation the next task was to move to Home screen. I tried segues, but that created problem for destination view. So at the end I accessed AppDelegates's Window property and I assigned a new NavigationController screen to it,
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let homeVC = storyboard.instantiateViewController(withIdentifier: "HomePageViewController") as! HomePageViewController
//Below's navigationController is useful if u want NavigationController in the destination View
let navigationController = UINavigationController(rootViewController: homeVC)
appDelegate.window!.rootViewController = navigationController
If incase, u don't want navigationController in the destination view then just assign as,
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let homeVC = storyboard.instantiateViewController(withIdentifier: "HomePageViewController") as! HomePageViewController
appDelegate.window!.rootViewController = homeVC
The above code works well but if you want to navigate from an NSObject class, where you can not use self.present:
let storyBoard = UIStoryboard(name:"Main", bundle: nil)
if let conVC = storyBoard.instantiateViewController(withIdentifier: "SoundViewController") as? SoundViewController,
let navController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
navController.pushViewController(conVC, animated: true)
}

Resources