Can I retrieve data from NSUserDefaults after app termination? - ios

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.

Related

How to switch the storyboard (Main or Auth) according to the user's login status

As mentioned in the title, I have a question about how to change the first screen shown to the user depending on whether the user is already registered for an account or not.
I'm developing an iOS application with login functionality.
I'm developing an iOS application with login functionality. (I am using AWS Amplify / AWS Cognito for this feature.) (I have already completed the user sign up and sign in process.)
We are currently creating two storyboards.
I have implemented user authentication to unregistered users in "Auth.storyboard" and I am. On the other hand, I'm trying to provide a memo feature for registered users in the "Main.storyboard".
In order to determine whether a user is registered or not, and to branch the screen accordingly, I wrote the following code in the "AppDelegate.swft".
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let ud = UserDefaults.standard
let isLogin = ud.bool(forKey: "LoginStatus")
if isLogin == true {
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let rootViewController = storyboard.instantiateViewController(withIdentifier: "home")
self.window?.rootViewController = rootViewController
self.window?.backgroundColor = UIColor.white
self.window?.makeKeyAndVisible()
} else {
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Authentcation", bundle: Bundle.main)
let rootViewController = storyboard.instantiateViewController(withIdentifier: "auth")
self.window?.rootViewController = rootViewController
self.window?.backgroundColor = UIColor.white
self.window?.makeKeyAndVisible()
}
//ellipsis........
return true
}
I use the UserDefault feature to get whether or not the user is already logged in, and then branch the screen by I think I've made the specification to do this. However, even though I'm not logged in, the "Main. storyboard" opens first, even though I'm not logged in.
Why is that?
I'd like to know what the solution is.
Also, if there is a better way to do this, I would like to know how to do it.
[addition]
Is it because you have selected "Main" in the testProject→General→Main Interface?
Delete 'Main' in YourProject > General > Main Interface and make sure to untick all 'Initial ViewController' properties if you have any in your StoryBoard's.

Swift storyboard login order

I am relatively new to Swift and I don't have much experience with the events and functions. After investigating a little I started working on a login screen on an existing app that didn't have it before. Here's what I decided to do.
My flow goes in this order:
Login screen
After successful login I save the session info
After login screen I show the main screen using a segue
If I close and reopen the app, my login screen is still the start screen, but inside viewDidAppear I have a session check and if the session exists I perform the segue to show the main controller.
I've seen that users do this the other way around - showing the main screen first and if there's no login session they cover it with the login screen or basically show the login screen first.
In my way of doing this, what I don't like is that the login screen always appears, although to be honest it does the job for this app in specific.
Is there a way to do this without the login screen appearing when there's a session? How is this ideally done in terms of order: login screen first or login screen second? And also, what is better to use, a navigation controller for the changes or segues are enough?
many here say is not recomended doing this way but i'll use appDelegate for this, in didFinishLaunchingWithOptions
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if requestManager.instance.token != nil {
if requestManager.instance.user.birthDate != nil && requestManager.instance.user.iscomplete() {
print("GOING TO TABBARVC")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "TabBarVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}else if requestManager.instance.user.birthDate != nil && !requestManager.instance.user.iscomplete(){
print("GOING TO DATANC")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "TabBarVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}else{
print("GOING TO REGISTER")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "register")
//loginRequest.instance.delegate = initialViewController as profileViewController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
}
so what i check here is for token saved in userdefault. could be checked from keycahin is it's critical, and depeding on what is found may show one or another screen, the last case is just in case where no data at all going to register or login screen.
so you could check for session is there is a session amke window show the main screen no session go to login screen
just like this in code:
if sesion != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "MainVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}else{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "loginVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
where MainVC is te id in the storyBoard for the mainViewcontroller and loginVC is the storyboard Idetntifier for loginViewController.

Firebase User Auth check in App delegate

I have implemented Firebase Auth and Firebase Database in app delegate which checks if the user is logged in or not.It checks in a 'users' node in Database to see if there is an userid in child snapshots or not and this operation takes about 2-3 seconds.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("App Delegate: User found!")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "Main")
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
else{
print("App Delegate: User not found!")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "Login")
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
})
}
return true
}
The Login viewController is the Initial viewController. When launch screen fades out, the login viewController is show and if the user is logged in, the Main viewController is shown but in this case, there is a time interval of 2-3 secs for which the Login viewController is being shown.
I tried to implement NavigationController too but same problem.
So how can I not show Login viewController if the user is logged in and go straight to the Main viewController and manage that 2-3 time seconds of time interval effectively.
Keep track isLogin variable in local session make it true and false while login and logout successful. When the first login then checks your session variable isLogin. You can save the session in UserDefault or Keychain. This way you can avoid your issue.
Or you just need to check this way:
if FIRAuth.auth().currentUser != nil {
presentHome()
} else {
//User Not logged in
}
After that when you navigated to desire viewController do rest of the things there.

iOS: NSUserDefault not loading fast enough in AppDelegate? Conditional fails intermittently

I'm using NSUserDefault in AppDelegate to decide which VC to show to a user first after they load my app.
If user hasn't set a specific setting, they go to VC 1 where they set that.
If user has the setting, they go to VC 2 and on.
My problem is that sometimes I'm being sent to VC1 when I know I have the setting.
This leads me to believe that NSUserDefaults are not being fully loaded before my conditional checks for the value.
Could this be possible and if so is there a way to "block" the load process until the variable is fully loaded?
Here's the relevant code from AppDelegate that I'm using:
class AppDelegate: UIResponder, UIApplicationDelegate {
let prefs = NSUserDefaults.standardUserDefaults()
var window: UIWindow?
func updateNavBar(navBar: UINavigationBar) {
navBar.barTintColor = UIColor(netHex:0x1d8696)
navBar.tintColor = UIColor(netHex:0xAEFFDD)
navBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor(netHex:0xAEFFDD),NSFontAttributeName : UIFont(name: "Lato-Bold", size: 16)!]
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
self.updateNavBar(UINavigationBar.appearance())
if let team = self.prefs.stringForKey("team")
{
print("Team prefs found. (" + team + ") User sent to IVList")
// show IVList VC
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) // this assumes your storyboard is titled "Main.storyboard"
let yourVC = mainStoryboard.instantiateViewControllerWithIdentifier("TabBarController") as! UITabBarController // inside "YOUR_VC_IDENTIFIER" substitute the Storyboard ID you created in step 2 for the view controller you want to open here. And substitute YourViewController with the name of your view controller, like, for example, ViewController2.
appDelegate.window?.rootViewController = yourVC
appDelegate.window?.makeKeyAndVisible()
}else{
// show editProfile VC
print("No team prefs found. User sent to profile Edit")
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) // this assumes your storyboard is titled "Main.storyboard"
let yourVC = mainStoryboard.instantiateViewControllerWithIdentifier("editProfile") as! EditProfileViewController // inside "YOUR_VC_IDENTIFIER" substitute the Storyboard ID you created in step 2 for the view controller you want to open here. And substitute YourViewController with the name of your view controller, like, for example, ViewController2.
appDelegate.window?.rootViewController = yourVC
appDelegate.window?.makeKeyAndVisible()
}
return true
}
I think it's a load timing thing because the issue is intermittent and inconsistent. It happens about 20% of the time.
I recently had exactly this issue.
The simple answer is that didFinishLaunchingWithOptions() in the appDelegate is only called AFTER the nib has been loaded for your initial view controller.
See the discussion at iOS - viewDidLoad is being called BEFORE the didFinishLaunchingWithOptions delegate?
Hope that helps!

Segue from LaunchScreen to a viewController in Main.Storyboard?

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.

Resources