popToRootViewController from NSObject Class - ios

I have been trying to find the solution but none of them available are working for me, Can someone please advise how can I achieve this?
I have set a rootviewcontroller as a view controller(Container Viewcontroller) by using the following code after successful login.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ContainerViewController") as! ContainerViewController
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window!.rootViewController = vc
Based on a specific notification received, I have defined a custom function which is being called for sure but I want my app user to go back to root automatically regardless of the navigation controller he is currently on. I am using this code
guard let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController else { return }
navigationController.popToRootViewController(animated: true)
Flow is Root(VC)->NavigationController1->PushingViewController2->PushingViewController3, suppose user is currently at ViewController3(which is pushed) and I want to take the user back from anotherClass NSOBject which is observing network Status and upon network failure it is calling ABC function but when I add the above navigation controller reference to pop all navigationcontrollers it is not working.

Related

How to move login page when session expired

I have multiple screens and api's, if session expired each api get session expired message. Based in that I'm moving from current page to login page.
My code :
//If session expaired move to login page
if message == "Session Expired" {
//Session Expired
DispatchQueue.main.async {
let lpc = self.storyboard?.instantiateViewController(withIdentifier: "LVC")
//Set the user login key false
UserDefaults.standard.set(false, forKey: "isUserLoggedIn")
//Clear user defaults
SharedClass.sharedInstance.clearDataFromUserDefaults()
self.navigationController?.pushViewController(lpc!, animated: false)
}
}
Here I'm using ** pushViewController** to navigate back. Every thing working fine, but when i'm in Nth VC is session expired it's navigating N times to login page. Means if I'm navigating from 1st VC to 2nd VC, 2nd VC to 33rd VC, 3rd VC to 4th, is session expired in 4th VC it's navigating to login page around 3times. How to resolve this issue....
You can try this
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LVC")
self.navigationController?.setViewControllers([vc], animated: true)
Since you didn't provide your full code so I am not sure why that issue happens. However, here are 2 solutions to achieve your requirement.
First implementation: Image for 1st flow
If the user logged in:
navigationController?.viewControllers = [loggedInVC]
If the user doesn't login yet or when he/she logout:
navigationController?.viewControllers = [logInVC]
2nd implementation: Image for 2nd flow
LoginVC is the root view controller and acts as a loading screen, and only show the login form when the session is invalid. With this implementation, every time the session is invalid. You can call:
navigationController?.popToRootViewController(animated: true)
to navigate the user back to the login screen.
First create the global variable to access the AppDelegate, because we are putting redirection function here.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
Now put below function in AppDelegate to set login screen whenever your session is expired.
func configureWindow(_ viewController: UIViewController) {
if let window = window {
window.rootViewController = viewController
window.makeKeyAndVisible()
}
}
now set your login UIViewController as like below.
let loginVC = LoginViewController()
appDelegate.configureWindow(loginVC)
use the following:
self.navigationController?.popToRootViewController(animated: false)
if login is not root,then you can use the following:
if let controllers = self.navigationController?.viewControllers, controllers.count > 0{
for vc in controllers{
if vc is LoginViewController{
self.navigationController?.popToViewController(vc, animated: false)
}
}
}
I don't know is this right or wrong. But i'm getting one more issue
1) First i cleared all navigations from navigation stack
2) Then i made my LoginVC as Root VC
3) Finally i called popToRootViewController
//If session expaired move to login page
if message == "Session Expired" {
DispatchQueue.main.async {
//Check navigation stacks
let navigationArray = self.navigationController?.viewControllers //To get all UIViewController stack as Array
print(navigationArray!)//Prints navigation stacks
//Remove all
self.navigationController!.viewControllers.removeAll()
//Check navigation stacks
let navigationArray2 = self.navigationController?.viewControllers //To get all UIViewController stack as Array
print(navigationArray2 as Any)//Prints nill
//Check whether the user logined or not
UserDefaults.standard.set(false, forKey: "isUserLoggedIn")
//Clear user defaults
SharedClass.sharedInstance.clearDataFromUserDefaults()
let lvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LVC") as! LoginViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = lvc
}
}
//Call alert function
self.showAlert(title: "", msg: message)
I don't know is this right or wrong. But i'm getting one more issue
Warning: Attempt to present <UIAlertController: 0x7fb840800600> on <******: 0x7fb840069800> whose view is not in the window hierarchy!

How to handle showing different view controllers when the app starts based on whether the user has logged in or not?

In an app that I am developing, I need to show different view controllers based on whether the user has logged in or not when the app starts
If the user has logged in, I need to show HomeViewController
If the user has not logged in, I need to show LoginViewController
Presently, I have coded in the following way-
the rootViewController of window of AppDelegate is always LoginViewController embedded inside a UINavigationController.
In the viewDidAppear method of LoginViewController, I check if the user has logged in. If yes, then I push HomeViewController.
In the HomeViewController, when the user logs out, I pop the HomeViewController to show LoginViewController
Is there a better way to do this?
Which is the better way is totally depends on your requirement and self-satisfaction.
Now think about your approach:
If you're going with this approach then login screen will display for fraction of time. If it is okay for you then you can for
it.
Now think about my approach:
I think the better way is to check the same condition in didFinishLaunchingWithOptions instead of loginVC
If you are going this approach then your HomeVC is directly displayed after your splash screen. It is the main advantage of this
approach
Code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let isLoggedIn = true
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let navigationController = storyboard.instantiateViewController(withIdentifier: "NavigationController") as! UINavigationController
var viewController:UIViewController
if !isLoggedIn{
viewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
}else{
viewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
}
navigationController.viewControllers = [viewController]
window?.rootViewController = navigationController
return true
}
Download sample code
Update for Anbu.Karthik:
Just define the method in AppDelegate.swift the file then calls it when required. Also, the same method is implemented in the sample code too.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.logoutFromApp()
Code to change the root view controller:
func logoutFromApp(){
guard let rootNavigationController = window?.rootViewController as? UINavigationController else {
return
}
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
rootNavigationController.viewControllers = [viewController]
}
Sometimes I use a custom root view controller, that can change a child view controller based on business logic (the root controller is a subclass of UIViewController not of UINavigationControler or UITabBarController). For example, the root controller can subscribe to notification about login/logout of user and show required view controller as a child view controller.
To support this, the root controller should be a custom container view controller (part with title Implementing a Container View Controller).
This solution allows to divide the interface to different story boards and save memory because you can destruct invisible view controllers.
I think the better and easier approach will be to check is user signed in in AppDelegate (the best approach to create separate class for this logic).
You need to store user token in keychain and in AppDelegate check is user signed in. If it's require API request, while request is executing, replace Splash screen with spinner and depends from the API response, display necessary controller

How to pass data to nth view controller on the UINavigationController stack?

There are 2 apps A & B.
App A has a url to open App B.
Soon as App B opens, it has to load 5 view controllers onto the navigation stack which is done by the following code:
let LandingVC = self.storyboard?.instantiateViewControllerWithIdentifier("LandingVC") as! LandingVC
let Dashboard = self.storyboard?.instantiateViewControllerWithIdentifier("Dashboard") as! Dashboard
let PlayerVC = self.storyboard?.instantiateViewControllerWithIdentifier("PlayerVC") as! PlayerVC
let PlayerDetailVC = self.storyboard?.instantiateViewControllerWithIdentifier("PlayerDetailVC") as! PlayerDetailVC
let ScoreReportVC = self.storyboard?.instantiateViewControllerWithIdentifier("ScoreReportVC") as! ScoreReportVC
let viewControllersList = [LandingVC, Dashboard, PlayerVC, PlayerDetailVC, ScoreReportVC]
self.navigationController?.setViewControllers(viewControllersList, animated: false)
From ScoreReportVC I need to be able to set variables on previous ViewControllers so that the user can navigate to previous screens even though they fired the application from another application.
Here is what I have tried: defined a protocol in previous view controllers that are behind ScoreReportVC that are on the stack and inside ScoreReportVC like so:
for viewcontroller in self.navigationController?.viewControllers {
if viewcontroller is PlayerDetailVC {
PlayerDetailVC.delegate = self
}
if viewcontroller is PlayerVC {
PlayerVC = self
}
if viewcontroller is Dashboard {
Dashboard.delegate = self
}
if viewcontroller is LandingVC {
LandingVC.delegate = self
}
}
But the delegates are not getting called. Any help how to correctly pass data to all the ViewControllers on the stack would be greatly appreciated.
For relatable classes we use Delegations. But the classes which are not relatable , for those, we use Notifications. In your case Notifications will be needed for implementation and to pass data from one VC to another VC.
A better approach would be to create/set the variables when the controllers are created.

how to fix the code of navigation controller

I spend 6 hours to solve the problem, but it still be like that.... I want to push my LoginController to MainViewController, and then, the complier pop out this red dot, it said that Arguement passed to call that takes no arguements. I hope everyone can do me a faver~~~~enter image description here
Try using instantiateViewController:
let mainViewController = self.storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
Also I think its better to use if let in the case the call fails
if let mainViewController = self.storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? DetailViewController {
}

How to connect Universal Links to UIViewController?

*Disclaimer: I've only been coding in iOS/XCode/Swift for a couple of weeks
I have Universal Links working in that clicking a link outside my app opens up the app and I catching the Url in the AppDelegate class.
So that's all good.
My question is... how do I then redirect to the correct UIViewController AND pass the controller some info from my URL? All Universal Link tutorials stop before they get to that part.
In particular I'm confused about the lifecycles of AppDelegate and how it relates to UIViewController.
My app had two UIViewController sitting under (is this right?) a UINavigationController.
What I've tried
I have tried handling the url event in AppDelegate, and setting a public property, and then in my ViewController getting access to the AppDelegate. HOWEVER, after the Universal Link is clicked, both viewDidLoad and viewWillAppear don't get called again :-/
What's the best way to redirect to a ViewController from AppDelegate? My goal is simply to load the root view controller BUT I need to pass in some data from the URL. How?
First, Read your URL.
Get your parameters from URL
Initiate your target controller
Set your parameter to that controller
Present controller on root view controller
let urlString = url.absoluteString
let queryArray = urlString.componentsSeparatedByString("/")
if queryArray[2].lowercaseString == "yourQuery" {
let queryId = Int(queryArray[3])
if self.window?.rootViewController?.presentedViewController != nil {
self.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
}
let queryVC = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(QUERY_SCENE) as? QueryViewController
queryVC?.urlQueryId = queryId!
self.window?.rootViewController?.presentViewController(queryVC!, animated: true, completion: nil)
}
Edit:
Push a controller say 'PresentedViewController' on navigation controller and if rootViewController is also navigation controller
And on back press on controller 'OnBackPressViewController' present controller 'PresentedViewController'
if self.window?.rootViewController?.presentedViewController != nil {
self.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
}
let navController = self.window?.rootViewController?.storyboard?.instantiateInitialViewController() as? UINavigationController
let presentedVC = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(PRESENTED_SCENE) as? PresentedViewController
//Pass parameters to PresentedViewController
let onBackPressViewController = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(ON_BACK_PRESS_SCENE) as? OnBackPressViewController
navController!.pushViewController(onBackPressViewController!, animated: true)
navController!.pushViewController(presentedVC!, animated: true)
self.window?.rootViewController?.presentViewController(navController!, animated: true, completion: nil)
You can update your code according to that. Always remember that you have to present any view controller on rootViewController.
It depends on your app architecture. You can definitely assume that the AppDelegate has the reference to the rootViewController (window?.rootViewController). You should know what's the type of that controller. Then you can access to the other and so on. This question is, anyway, too much generic in my opinion.

Resources