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

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!

Related

How to set variable value through AppDelegate in Swift?

I have UIViewController in Main.storyboard and set it's identifier to settings. I reference Main.storyboard and get the view controller in AppDelegate and then set the value of a variable which is located in view controller but it returns nil.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let settingViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "settings") as UIViewController? {
let settingVC = settingViewController as! SettingViewController
settingVC.moc = persistendContainer.viewContext
}
return true
}
And I have following variable in SettingViewController:
var moc: NSManagedObjectContext!
I confirmed that I am able to get Main.storyboard, SettingViewController and persistentContainer.viewContext has value.
You are instantiating a new settings UIViewController successfully, but then nothing happens with it after that. Later on your app will create a Settings viewController through the storyboard, but that will be a different one.
The better way to do this is to create an accessible place for your viewController to access the moc variable rather than trying to pass it in in advance. This is usually done for you if you let Xcode create the project with CoreData set up. You can say (UIApplication.shared.delegate as! AppDelegate).managedObjectContext from any viewController in your app and get a pointer to that moc.

Open the last opened view controller when closing and opening the app

I want to be able to close and reopen the same view controller on my app. The reason I want to do this is because I have a "tutorial" scene that I only want to be brought up the first time you open the app. Every other time I want the app to instantly open up the second view controller. Here is my code:
for GameViewController:
UserDefaults.standard.set(0, forKey: "View")
for GameViewController2:
UserDefaults.standard.set(1, forKey: "View")
for AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let viewCount = UserDefaults.standard.integer(forKey: "View")
var VC = UIViewController()
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
print(viewCount)
if viewCount == 0 {
//FirstView should initiate
VC = storyboard.instantiateViewController(withIdentifier: "First") as! GameViewController
} else {
//SecondView should inititate
VC = storyboard.instantiateViewController(withIdentifier: "Second") as! GameViewController2
}
self.window?.makeKeyAndVisible()
self.window?.rootViewController = VC
return true
}
The problem is that every single time I open the app, it always starts on my first view controller instead of my second view controller. Any help would be appreciated.
Based on the code above, you are not calling UserDefaults.synchronize
var userDefaults = UserDefaults.standard
userDefaults.set(1, forKey:"View")
userDefaults.synchronize()
In both cases it must be synchronised as this saves changes to disk
Update: As noted by #cs4alhaider synchronize() is not needed in Swift 4, so this problem would not occur anymore.

AddingViewcontroller manually into the stack swift 3

I am coding the sign out button in my app.This is the flow of my app.
SplashvwController -> secretCodeViewController ->
LoginviewController -> DashboardViewController
in this DashboardViewController I have the signOut button.
My app has single sign in facility once user logged, next time when he opens the app,
SplashvwController -> DashboardViewController
I want to prompt the user to Loginviewcontroller whenever he clicks the sign out button.
Question
When user going through path 1 I can simply do popviewcontroller to go back to the previous viewcontroller. But when user go though the 2nd path,
how can I add the Loginviewcontroller manually into my
viewcontrollers stack to perform the same operation?
How can I check whether the LoginviewController exists in my current Viewcontrollers stack?
Please help me
I think, the below code helps you,
for (var i = 0; i < self.navigationController?.viewControllers.count; i++) {
if(self.navigationController?.viewControllers[i].isKindOfClass(Loginviewcontroller) == true) {
self.navigationController?.popToViewController(self.navigationController!.viewControllers[i], animated: true)
break;
}
}
To add a ViewController Manually , check the code below..
let navigation = mainStoryboard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
let nav = UINavigationController(rootViewController: navigation)
appdelegate.window!.rootViewController = nav
Set the storyboard reference identifier for a LoginViewController on the Main.Storyboard file.
Whenever you want to show the LoginViewController just call the function
func launchLoginView() {
if let loginVC: LoginViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginStoryboardID") as? LoginViewController {
// .instantiatViewControllerWithIdentifier() returns AnyObject! this must be downcast to utilize it
// LoginStoryboardID is the reference id for login view controller.
self.presentViewController(loginVC, animated: true, completion: nil).
// OR
//UIApplication.shared.keyWindow?.rootViewController = loginVC
}
}
Landing screen based on the User login status.
In AppDelegte.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let isLoggedAlready = //Get the login status
if isLoggedAlready == true {
if let dashBoardVC: DashboardViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("DashboardStoryboardID") as? DashboardViewController {
//Already logged in then directly launch dashboard vc.
//change the code based on your needs
window?.rootViewController = dasbBoardVC
}
}
// Otherwise let it go as flow 1
return true
}
}

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.

IOS Change rootview controller after login

I have implemented a side drawer in my application which required me to set the root view controller in my application delegate. i am now working on a login screen. to access the login screen i had to comment out where i set the root view controller. How can i set the root view controller once the user has logged in?
Below is my app delegate(or part of it)
AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let centerViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as!ViewController
let drawerViewController = mainStoryboard.instantiateViewControllerWithIdentifier("DrawerViewController") as!DrawerViewController
let leftSideNav = UINavigationController(rootViewController: drawerViewController)
let centerNav = UINavigationController(rootViewController: centerViewController)
centerContainer = MMDrawerController(centerViewController: centerNav, leftDrawerViewController: leftSideNav)
centerContainer!.openDrawerGestureModeMask = MMOpenDrawerGestureMode.PanningCenterView;
centerContainer!.closeDrawerGestureModeMask = MMCloseDrawerGestureMode.PanningCenterView;
//window!.rootViewController = centerContainer
window!.makeKeyAndVisible()
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
}
i have a method to handle the segue after successful login but don't know how to set the root controller out side the delegate.
in summary the navigation works if i uncomment the //window!.rootViewController = centerContainer line but then cant access the login view
You can swap the root view controller dynamically:
self.view.window!.rootViewController = newVc
A simple example can be found here: https://stackoverflow.com/a/34951197/218152.
A complete example can be found here: https://stackoverflow.com/a/32109767/218152.
Swapping the rootViewController is a one time operation: no back button, no animation.
To change rootViewController after login screen
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
UIApplication.shared.windows.first?.rootViewController = viewController
UIApplication.shared.windows.first?.makeKeyAndVisible()
Make sure you have set 'HomeViewController' in storyboard as Storyboard ID of HomeViewController

Resources