remember user/password and auto login firebase - swift - swift

Hi i have an application that connected to the Firebase and i have two viewController, first viewController is for log in , the second one is a tableViewController. i want to let user log in automatically when they open the application. i mean when they close the Application and open it again i want the Application log in the user automatically. I tried to use firebase documentation in AppDelegate firebase and i tried to use SwiftKeychainWrapper but it didn't help me.
this is my code for the logInController :
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class logInViewController: UIViewController, UITextFieldDelegate, UITableViewDelegate {
#IBOutlet var userNameField: UITextField!
#IBOutlet var passwordField: UITextField!
#IBOutlet var logInButton: UIButton!
var db: Firestore!
override func viewDidLoad() {
super.viewDidLoad()
logInButton.layer.cornerRadius = 4.0
userNameField.delegate = self
passwordField.delegate = self
}
#IBAction func logInButtonClicked(_ sender: Any) {
Auth.auth().signIn(withEmail: (userNameField.text ?? ""), password: (passwordField.text ?? "")) { (result, error) in
if let _eror = error{
let alert = UIAlertController(title: "Error", message: error!.localizedDescription, preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(okAction)
self.present(alert,animated: true)
print(_eror.localizedDescription)
}else{
if let _res = result{
print(_res)
}
let VC1 = self.storyboard!.instantiateViewController(withIdentifier: "order1") as! OrderTableViewController
self.navigationController!.pushViewController(VC1, animated: true)
}
}
}
}
this is my AppDelegate code :
class AppDelegate: UIResponder, UIApplicationDelegate {
var db: Firestore!
var firebaseToken: String = ""
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
self.registerForFirebaseNotification(application: application)
Messaging.messaging().delegate = self
if Auth.auth().currentUser != nil {
//======
let homeController = OrderTableViewController()
self.window?.rootViewController = homeController
self.window?.makeKeyAndVisible()
} else {
let logInController = logInViewController()
self.window?.rootViewController = logInController
self.window?.makeKeyAndVisible()
}
return true
}

Firebase already persists the user credentials and restores them when the app restarts. But since this requires a call to the server, it happens asynchronously and probably hasn't completed yet by the time your Auth.auth().currentUser runs.
The solution is to use an auth state listener, as shown in the first snippet in the documentation on getting the current user:
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if user != nil {
let homeController = OrderTableViewController()
self.window?.rootViewController = homeController
self.window?.makeKeyAndVisible()
} else {
let logInController = logInViewController()
self.window?.rootViewController = logInController
self.window?.makeKeyAndVisible()
}
}

Firebase store all info about auth and user locality in phone.
try this code, it worked in my app
if let _ = Auth.auth().currentUser {
//======
// Load user data (profile) from firebase if needed
//======
let homeController = OrderTableViewController()
self.window?.rootViewController = homeController
self.window?.makeKeyAndVisible()
} else {
let rootController = firstViewController()
self.window?.rootViewController = rootController
self.window?.makeKeyAndVisible()
}
for use UserDefaults:
if let id = UserDefaults.standard.string (forKey: "userId") as? String, id != "" {
//======
// Load user data (profile) from firebase if needed
//======
let homeController = OrderTableViewController()
self.window?.rootViewController = homeController
self.window?.makeKeyAndVisible()
} else {
let rootController = firstViewController()
self.window?.rootViewController = rootController
self.window?.makeKeyAndVisible()
}

Related

How to place a VC in a navigation controller programmatically?

I am currently getting an error whenever I try to present this VC. The error is Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value I believe it is because the VC is not in a navigation controller. How do I put this VC in a navigation controller(programmatically).
import UIKit
import AWSAuthCore
import AWSAuthUI
import AWSMobileClient
class SignInViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
AWSMobileClient.default()
.showSignIn(navigationController: self.navigationController!, // this is where i get the error.
signInUIOptions: SignInUIOptions(
canCancel: true,
logoImage: UIImage(named: "MyCustomLogo"),
backgroundColor: UIColor.red)) { (result, err) in
AWSMobileClient.default().initialize { (userState, error) in
if let userState = userState {
switch(userState){
case .signedIn:
DispatchQueue.main.async {
}
case .signedOut:
AWSMobileClient.default().showSignIn(navigationController: self.navigationController!, { (userState, error) in
if(error == nil){ //Successful signin
DispatchQueue.main.async {
}
}
})
default:
AWSMobileClient.default().signOut()
}
} else if let error = error {
print(error.localizedDescription)
}
}
}
}
}
Here is the code from AppDelegate
import UIKit
import AWSAppSync
import AWSMobileClient
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var appSyncClient: AWSAppSyncClient?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let nav1 = UINavigationController()
let mainView = SignInViewController(nibName: nil, bundle: nil)
nav1.viewControllers = [mainView]
self.window!.rootViewController = nav1
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController(rootViewController: TabBarController())
window?.makeKeyAndVisible()
AWSMobileClient.default().addUserStateListener(self) { (userState, info) in
switch (userState) {
case .guest:
print("user is in guest mode.")
case .signedOut:
self.window?.rootViewController?.present(SignInViewController(), animated: true, completion: nil)
print("user signed out.")
case .signedIn:
print("user is signed in.")
case .signedOutUserPoolsTokenInvalid:
print("need to login again.")
case .signedOutFederatedTokensInvalid:
print("user logged in via federation, but currently needs new tokens")
default:
print("unsupported")
}
}
AWSMobileClient.default().initialize { (userState, error) in
if let userState = userState {
print("UserState: \(userState.rawValue)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
}
do {
// You can choose the directory in which AppSync stores its persistent cache databases
let cacheConfiguration = try AWSAppSyncCacheConfiguration()
// AppSync configuration & client initialization
let appSyncServiceConfig = try AWSAppSyncServiceConfig()
let appSyncConfig = try AWSAppSyncClientConfiguration(appSyncServiceConfig: appSyncServiceConfig,
cacheConfiguration: cacheConfiguration)
appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncConfig)
print("Initialized appsync client.")
} catch {
print("Error initializing appsync client. \(error)")
}
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
Embed the SignInViewController in a UINavigationController.
To do that, drag-and-drop a UINavigationController to the Storyboard and CTRL + drag from that UINavigationController to the SignInViewController, choose the relationship rootViewController
Programatically:
var nav1 = UINavigationController()
var mainView = SignInViewController(nibName: nil, bundle: nil)
nav1.viewControllers = [mainView]
self.window!.rootViewController = nav1

Swift: Passing managedObjectContext from AppDelegate to UINavigationController

I have some iOS project (Project) with an #escaping method in the AppDelegate that is used to create the NSPersistentContainer for this project. This method is then called within the didFinishLaunchingWithOptions method as follows:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var persistentContainer: NSPersistentContainer!
func createProjectContainer(completion: #escaping (NSPersistentContainer) -> ()) {
let container = NSPersistentContainer(name: "Project")
container.loadPersistentStores { (_, error) in
guard error == nil else { fatalError("Failed to load store: \(error!)") }
DispatchQueue.main.async { completion(container) }
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
createProjectContainer { container in
self.persistentContainer = container
let storyboard = self.window?.rootViewController?.storyboard
guard let vc = storyboard?.instantiateViewController(withIdentifier: "NavigationController") as? NavigationController else {
fatalError("Cannot instantiate root view controller")
}
vc.managedObjectContext = container.viewContext
self.window?.rootViewController = vc
}
return true
}
}
The NavigationController class is simply a subclass of UINavigationController, which embeds a UIViewController (ChildViewController) and has an NSManagedObjectContext variable (managedObjectContext), which I want to pass to the first ChildViewController:
class NavigationController: UINavigationController {
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
guard let vc = self.childViewControllers.first as? ChildViewController else { fatalError("") }
vc.managedObjectContext = managedObjectContext
print("NavigationController: viewDidLoad")
print("managedObjectContext ---> \(managedObjectContext)")
}
}
Now the print("NavigationController: viewDidLoad") appears to be called twice. On the first call, managedObjectContext = nil, on the second call it has been assigned. Does this matter that the app is loading this twice?
It seems to be happening during the storyboard?.instantiateViewController(withIdentifier: "NavigationController") which is called after the first time that NavigationController has been loaded due to the #escaping property of the closure. However, if I exclude that line, as just get the reference to the NavigationController the managedObjectContext appears never to be assigned.

'Could not find a storyboard named 'MainTabController' in bundle NSBundle

the error I'm receiving that I can't seem to fix is
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'Could not find a storyboard
named 'MainTabController' in bundle NSBundle
the app will build and the login screen will display but crashes immediately after with the error stated above.
I have tried all of the following from other post similar to this and have had no luck.
Removing the reference to the storyboard in the info.plist file. When I do this the app does start but I get a black screen as it doesn't load the storyboard.
Fiddle with the Target Membership Main.storyboard.
Remove the storyboard from the project, clean, run and then adding the storyboard back again.
Uninstall Xcode, reinstall Xcode.
Deleting the Derived Data folder.
the problem appears to be with my presentMainScreen() method
func presentMainScreen(){
let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
let mainTabController = mainstoryboard.instantiateViewController(withIdentifier: "MainTabController") as! MainTabController
mainTabController.selectedViewController = mainTabController.viewControllers?[1]
//let storyboard:UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
//let loggedInVC:LoggedInVC = storyboard.instantiateViewController(withIdentifier: "LoggedInVC") as! LoggedInVC
//self.present(loggedInVC, animated: true, completion: nil)
}
if I comment out the mainTabController lines the app will work perfectly, also if I uncomment the loggedInVC lines and with the mainTabController lines commented out it works perfectly as well.
any suggestions are greatly appreciated.
below is my entire ViewController.swift code and a screenshot of my workspace
workspace
ViewController.swift
import UIKit
import FirebaseAuth
class ViewController: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
if Auth.auth().currentUser != nil {
print("success")
self.presentMainScreen()
}
}
#IBAction func creatAccountTapped(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text {
Auth.auth().createUser(withEmail: email, password: password, completion:{ user, error in
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
}
print("success")
self.presentMainScreen()
})
}
}
#IBAction func loginButtonTapped(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text {
Auth.auth().signIn(withEmail: email, password: password, completion: { (user, error) in
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
}
print("success")
self.presentMainScreen()
})
}
}
func presentMainScreen(){
let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
let mainTabController = mainstoryboard.instantiateViewController(withIdentifier: "MainTabController") as! MainTabController
mainTabController.selectedViewController = mainTabController.viewControllers?[1]
//let storyboard:UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
//let loggedInVC:LoggedInVC = storyboard.instantiateViewController(withIdentifier: "LoggedInVC") as! LoggedInVC
//self.present(loggedInVC, animated: true, completion: nil)
}
}
What is the name of your storyboard? Is it MainTabController.storyboard or Main.storyboard?
You are trying to load a storyboard named "MainTabController":
let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
But previously you called it Main.storyboard:
Fiddle with the Target Membership Main.storyboard.
Also if the main tab controller is in the same storyboard as your login view controller then you can try to use this:
let mainTabController = self.storyboard.instantiateViewController(withIdentifier: "MainTabController")
Your story board is named as Main.storyboard. MainTabController is the controller in the Main storyboard.
let mainstoryboard = UIStoryboard(name: "Main", bundle: nil)
let mainTabController = mainstoryboard.instantiateViewController(withIdentifier: "MainTabController") as! MainTabController
mainTabController.selectedViewController = mainTabController.viewControllers?[1]
let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
Your storyboard is named "Main"

Blank screen first time launching app xcode

I am having a little issue with logging into an app the first time with a blank screen and I am getting the warning "Attempt to present on whose view is not in the window hierarchy!" After I close and relaunch, the views appear fine. I believe it has something to do with the rootViewController but not sure... Thanks in advance for any help or direction!
App delegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var ref:FIRDatabaseReference?
var databaseHandle:FIRDatabaseHandle?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationController()
FIRApp.configure()
ref = FIRDatabase.database().reference()
return true
}
Navigation controller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// log in function called
func finishLoggingIn() {
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
guard let mainNavigationController = rootViewController as? MainNavigationController else { return }
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
mainNavigationController.viewControllers = [HomeController()]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
UserDefaults.standard.setIsLoggedIn(value: true)
dismiss(animated: true, completion: nil)
}
Ok, so my last answer was wrong (i deleted it), the things is that in app there is a keywindow, which is your navcontroller, and you cannot present anything on this one untill it will load its subviews (even if it hasn't any), and that would happend on viewdidappear, so you should put your code from viewdidload there.

Swift project not segue-ing properly after Facebook login

The initial ViewController, LoginViewController.swift:
import UIKit
class LoginViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
if (FBSDKAccessToken.currentAccessToken() != nil)
{
// User is already logged in, do work such as go to next view controller.
performSegueWithIdentifier("loginSegue", sender: nil)
print("segued due to login")
}
else
{
let loginView : FBSDKLoginButton = FBSDKLoginButton()
self.view.addSubview(loginView)
loginView.center = self.view.center
loginView.readPermissions = ["public_profile", "user_friends"]
loginView.delegate = self
}
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
print("User Logged In")
if ((error) != nil)
{
// Process error
}
else if result.isCancelled {
// Handle cancellations
}
else {
// If you ask for multiple permissions at once, you
// should check if specific permissions missing
performSegueWithIdentifier("loginSegue", sender: nil)
/*
if result.grantedPermissions.contains("email")
{
// Do work
}
*/
}
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
print("User Logged Out")
}
}
"segued due to login" is printed to the terminal upon starting up the app every time, so the if-statement is clearly being reached and also the performSegueWithIdentifier() line. However, the segue is not actually performed as the LoginViewController stays on the screen and the next ViewController is not displayed. I have also tried adding the line:
performSegueWithIdentifier("loginSegue", sender: nil)
in several other locations I know the program is reaching, like right after super.viewDidLoad(). So, the problem seems to be specific to the segue and the problem does not seem to be with Facebook's login.
I have included a screenshot of the storyboard with the segue's attributes:
I can include any other files if needed. I also suspect it could be a similar type bug as this stackoverflow problem. I have tried deleting the placeholders in my UITextViews in all of my ViewControllers, but this did not solve the problem.
Ok so here's how your application:didFinishLaunching:withOptions should look like.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var initialViewController: UIViewController
if(FBSDKAccessToken.currentAccessToken() != nil){
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("someOtherViewController") as! SomeOtherViewController
initialViewController = vc
}else{
initialViewController = mainStoryboard.instantiateViewControllerWithIdentifier("loginViewController")
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}