What is the best way to log out from a TabBar. I have an app which in one of its tabs has a log out option in a row. When I tab this row I want to go to my log in screen. The app launch the log in screen if you are not log in or the main screen (is a tab bar controller) if you are already log gin. I manage this in the appDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let defaults = NSUserDefaults.standardUserDefaults()
let notFisrtRun = defaults.boolForKey("notFirstRun")
// Chooses between login view and my list view if the user is already authenticated when the app is launched
if AuthToken.sharedInstance.isAuthenticated() && notFisrtRun {
let tabBarViewController = mainStoryboard.instantiateViewControllerWithIdentifier("TabBarViewController") as? UITabBarController
self.window?.rootViewController = tabBarViewController
} else {
let loginViewController = mainStoryboard.instantiateViewControllerWithIdentifier("LoginViewController") as? LoginViewController
self.window?.rootViewController = loginViewController
}
self.window?.makeKeyAndVisible()
return true
}
If I try a show segue to my log in screen, it appear with the navigation bar and tab bar.
If I try an unwind segue, it work in the case you are not already log in and the app start with log in screen.But in the cases the app start with the tab bar controller because you are already log in, it doesn't work.
It is pretty normal for apps to show login screens modally. Upon logout, Present loginViewController modally. If the user logs back in, dismiss the loginViewController.
You could also create a custom segue that replaces the rootViewController, This question can get you started on that.
Related
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
I have a situation where if the user is not logged in to Firebase, when they launch my app, a modal viewcontroller is presented asking them to log in.
I am also using UIActivityViewController to send data between devices, but want to notify the user with an alert if he is not logged in.
I figure I can do this from the AppDelegate in the open url application function:
func application(_ app: UIApplication, open url: URL, ...
I just have to figure out how to navigate to that viewcontroller to call the function that will present my alert.
Within this function, I have this:
if status == .notLoggedIn {
guard
let rootVC = window?.rootViewController as? UITabBarController,
let collectionSplitVC = rootVC.viewControllers?.first as? UISplitViewController,
let navVC = collectionSplitVC.viewControllers.first as? UINavigationController,
let MyBookshelfCollectionVC = navVC.children.first as? MyBookshelfCollectionVC
// here is where I want to get access to the modal viewController presented on this MyBookshelfCollectionVC.
// it is called LoginVC
else { return true }
LoginVC.showAlertFromAppDelegate(status: status)
}
So the question his, how do I access this modal viewcontroller?
I was wondering where NSUserDefaults is writing all the stored data to: to the app memory or to the device the app is running on? In the latter case, I guess I should be able to retrieve stored data from NSUserDefaults even if the app has been completely shut down, right? Basically what I'm trying to do: at runtime I want to check whether or not the user was logged in when he/she shut down the app (which I store in NSUserDefaults as a Boolean at the moment a user logs in or logs out), and if the user was logged in, I want to set a different root view controller from the original root view controller a user would see if the app was opened for the first time. I've tried a lot of different things, but nothing seems to work when I test it on my device. Is NSUserDefaults the best approach to do this (if so, then how?) or are there better alternatives to do this?
When using 'NSUserDefaults' for something simple like this it will work well for what you are trying to achieve.
This is the code you would place in your AppDelegate that will handle sending the user to the correct view based on the value.
Swift 2
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let logonStatus = NSUserDefaults.boolForKey("LogonStatus")
if logonStatus {
print("User logged in.")
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewControllerWithIdentifier("navController") as! UINavigationController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
} else {
print("User not Logged in.")
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewControllerWithIdentifier("loginViewController") as UIViewController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
return true
}
Then in your logout or logon viewController or the code to perform the logout or logon you would just need to make sure to set the 'NSUsersDefaults' value.
NSUserDefaults.setBool(true, forKey: "logonStatus") // Logged On
NSUserDefaults.setBool(false, forKey: "logonStatus") // Logged Off
Swift 3
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let logonStatus = UserDefaults.standard.bool(forKey: "LogonStatus")
if logonStatus {
print("User logged in.")
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "navController") as! UINavigationController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
} else {
print("User not Logged in.")
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "loginViewController") as UIViewController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
return true
}
Set 'UserDefaults'
UserDefaults.standard.set(true, forKey: "LogonStatus")
You've got a few questions here. NSUserDefaults is persistent across app launches, per this other answer on StackOverflow: Data persistence through NSUserDefaults. However, once you delete the app, you will lose the data.
In terms of programming for a root view controller, I would say yeah, if you want to show the login root view controller, that's a pretty strong case for storing login persistence in NSUserDefaults. Just don't store their login directly in the defaults of course :)
One thing I would note is that it quickly gets hard to reason about view hierarchy and you can introduce pretty nasty bugs if the root view controller is changing. Like, once the user logs in, what do you do with the old root view controller? Instead of what you describe, I'd keep the same root view controller, but present a modal for login over the top of the root if a login is needed. Simpler logic and your app will be more stable.
I got some logic I want to do in my LaunchScreen, and if the check is alright, I want to segue to a viewController and if not I want to segue to another, is that possible?
I got some logic I want to do in my LaunchScreen
Now that I understand the question, I can answer it: don't. Do your time-consuming logic later. Your job is to launch fast. You need to get out of applicationDidFinishLaunchingWithOptions, get out of viewDidLoad, and launch.
What you show at that point is up to you; if you have time-consuming stuff (in your case, it sounds like you're networking or doing something else that takes time while you load up the data source for a table) and you want to show a special view controller that covers the time with a spinning activity view or something, fine. But during actual launch is not the time to do that.
No you cant code for launchScreen.Storyboard, The reason why :- when your launchScreen.storyboard shows the app is still loading.
Simply put: You cant access your app when it is displaying launchScreen.storyboard, all you can do is make a UI/UX for that not execute any code for it.
Alternative:- Make a viewController that appears as a first viewController check your logic there and do things from there accordingly!
Reference : - https://stackoverflow.com/a/27642160/6297658
Your LaunchScreen is shown while your app is loading. Go to your AppDelgate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
window.rootViewController = //your root view controller that you have figured out with logic
return true
}
Run your check in didFinishLaunchingWithOptions() and use that to "jump" directly to a specific vc. Here's an example using userDefaults, but of course you can replace that with whatever check you're running.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Do some logic
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let welcomeVC = storyboard.instantiateViewControllerWithIdentifier("WelcomeNavController")
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = welcomeVC
self.window?.makeKeyAndVisible()
}
}
Add this function into the AppDelegate:
func initialVC(storyboardID: String) {
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("\(storyboardID)") as UIViewController
self.window?.makeKeyAndVisible()
if storyboardID == "tabBarVC" {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = initialViewController
} else {
let navController = UINavigationController(rootViewController: initialViewController)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = navController
}
}
In the didFinishLaunchingWithOptions method inside of the AppDelegate, you can add this:
if currentUser != nil {
initialVC("tabBarVC")
} else {
initialVC("loginVC")
}
You can see in my example, I am either loading the main app VC or the Login VC depending on if the user is logged in. In your case, You can use an if - else statement and do the logic within the initialVC function.
Note: When I call for the loginVC to be loaded, I have to load the navigationController because the loginVC is embedded in a navigationController. For the tabBarVC, I don't embed the navController because it isn't needed.
I am working on an app that has two Storyboards: One for my app's onboarding controllers and the other for my main app. My onboarding view controller has a skip button that when pressed directs the user to the main storyboard. I only want to show the onboarding view as long as the user hasn't hit the skip button. Once the skip button is hit, the corresponding storyboard should disappear forever.
I thought I could fix this problem by only making showing onboarding storyboard when the app is first opened, and I found some code online that seemed helpful, however it doesn't work...here is the code that is in my app's AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
Fabric.with([Crashlytics.self])
let defaults = NSUserDefaults.standardUserDefaults()
if (defaults.objectForKey("userAsLoggedInBefore") != nil) {
print("Functions")
//here you insert the code to go to the main screen
var mainView: UIStoryboard!
mainView = UIStoryboard(name: "Functions", bundle: nil)
let viewcontroller : UIViewController = mainView.instantiateViewControllerWithIdentifier("functions") as UIViewController
self.window!.rootViewController = viewcontroller
} else {
//this means the user hasn't logged in before and you can redirect him to the onboarding page
print("Onboarding")
var mainView: UIStoryboard!
mainView = UIStoryboard(name: "Main", bundle: nil)
let viewcontroller : UIViewController = mainView.instantiateViewControllerWithIdentifier("onboarding") as UIViewController
self.window!.rootViewController = viewcontroller
}
}
Note in my code "Main" is the onboarding storyboard, while "Functions" is my main app code.
However this code is not working as intended as my same problem still arises. If anybody could take a look at my code and see if I am doing anything wrong, that would be greatly appreciated.
Thanks in advance!
You are never writing to NSUser defaults. So it will stay nil..
Your missing some line like this:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue("Did watch", forKey: "userAsLoggedInBefore")
Put it at the end of if - else statement