Segue to home screen if Anonymous user exists on the database - swift

I am using PFAnonymousUtils and PFUser.enableAutomaticUser. I want to check if the user exist is the database, if so i want to pivot to DiscoverViewController.
The problem is that even if there is no user the app keeps forwarding to DiscoverVc...
let currentUser = PFUser.currentUser()
if currentUser != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// instantiate your desired ViewController
let rootController = storyboard.instantiateViewControllerWithIdentifier("DiscoverVc")
// Because self.window is an optional you should check it's value first and assign your rootViewController
if self.window != nil {
self.window!.rootViewController = rootController
}
} else {
let installation = (PFInstallation.currentInstallation())
installation["User"] = PFUser.currentUser()
installation.saveInBackground()
PFAnonymousUtils.logInWithBlock {
(user: PFUser?, error: NSError?) -> Void in
if error != nil || user == nil {
print("Anonymous user failed")
} else {
print("Anonymous user logged in.")
}
}
}

From parse documentation:
PFUser.enableAutomaticUser
After calling this method, currentUser will always have a value. The
user will only be created on the server once the user has been saved,
or once an object with a relation to that user or an ACL that refers
to the user has been saved.
To check if the user is an Anonymous user use:
PFAnonymousUtils.isLinkedWithUser(PFUser.currentUser())
Return Value YES if the user is anonymous. NO if the user is not the
current user or is not anonymous.

Related

How to check if email already exist before creating an account (Swift)

I know different variations of this question have been asked. However I seem to keep running into the same issue every time.
I want to check if an email already exist before the user pushes onto the next view. I will enter an email that exist in the database and the performSegue func is always called and pushes the user as if that email does not exist.
The only way I can check officially is when the user reaches the final sign up VC and the Auth.auth().createUser(withEmail: email as! String, password: password as! String ) { (user, error) in code will check for all errors.
However for good user experience I would hate for the user to have to click back three times to change the email address. Here is the code I have for the enter email view controller.
// Check if email is already taken
Auth.auth().fetchSignInMethods(forEmail: emailTextField.text!, completion: { (forEmail, error) in
// stop activity indicator
self.nextButton.setTitle("Continue", for: .normal)
self.activityIndicator.stopAnimating()
if let error = error {
print("Email Error: \(error.localizedDescription)")
print(error._code)
self.handleError(error)
return
} else {
print("Email is good")
self.performSegue(withIdentifier: "goToCreateUsernameVC", sender: self)
}
})
First off am I even entering the create property in the forEmail section? I added emailTextField.text because its the only way I know how even get the email the user typed. Does anyone know a better way I can do this?
How I create user accounts
This is an example of what I use. When a user provides credentials, FirebaseAuth checks if these credentials can be used to make a user account. The function returns two values, a boolean indicating whether the creation was successful, and an optional error, which is returned when the creation is unsuccessful. If the boolean returns true, we simply push to the next view controller. Otherwise, we present the error.
func createUserAcct(completion: #escaping (Bool, Error?) -> Void) {
//Try to create an account with the given credentials
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordConfirmTextField.text!) { (user, error) in
if error == nil {
//If the account is created without an error, then we will make a ProfileChangeRequest, i.e. update the user's photo and display name.
if let firebaseUser = Auth.auth().currentUser {
let changeRequest = firebaseUser.createProfileChangeRequest()
//If you have a URL for FirebaseStorage where the user has uploaded a profile picture, you'll pass the url here
changeRequest.photoURL = URL(string: "nil")
changeRequest.displayName = self.nameTextField.text!
changeRequest.commitChanges { error in
if let error = error {
// An error happened.
completion(false, error)
} else {
//If the change is committed successfully, then I create an object from the credentials. I store this object both on the FirebaseDatabase (so it is accessible by other users) and in my user defaults (so that the user doesn't have to remotely grab their own info
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
//Store the object as data in my user defaults
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey: "UserData")
UserDefaults.standard.set([Data](), forKey: "UserPhotos")
completion(true, nil)
}
}
}
} else {
// An error happened.
completion(false, error)
}
}
}
Here is an example of how I would use it. We can use the success boolean returned to determine if we should push to the next view controller, or present an error alert to the user.
createUserAcct { success, error in
//Handle the success
if success {
//Instantiate nextViewController
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let nextVC = storyboard.instantiateViewController(withIdentifier: "NextVC") as! NextViewController
//Push typeSelectVC
self.navigationController!.pushViewController(viewController: nextVC, animated: true, completion: {
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
})
} else {
//We now handle the error
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
//Create a UIAlertController with the error received as the message (ex. "A user with this email already exists.")
let alertController = UIAlertController(title: "Error", message: error!.localizedDescription, style: .alert)
let ok = UIAlertAction(title: "OK", style: .cancel, action: nil)
//Present the UIAlertController
alertController.addAction(ok)
self.present(alertController, animated: true, completion: nil)
}
}
Let me know if this all makes sense, I know there is a lot to it. I'm just considering things you'll maybe find you need done anyways that you may not be aware of (like making change requests, or storing a data object on FirebaseDatabase).
Now for checking if the email is already taken:
Remember when I said that I post a user object to FirebaseDatabase upon account creation? Well we can query for the given email to see if it already exists. If it doesn't we continue with the flow as normal, without having actually created the account. Otherwise, we simply tell the user to pick another email address.
Pushing a user object to your database (taken from the above code):
if let firebaseUser = Auth.auth().currentUser {
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
}
And now querying to see if somebody already has that email:
func checkIfEmailExists(email: String, completion: #escaping (Bool) -> Void ) {
Database.database().reference().child("Users").queryOrdered(byChild: "email").queryEqual(toValue: email).observeSingleEvent(of: .value, with: {(snapshot: DataSnapshot) in
if let result = snapshot.value as? [String:[String:Any]] {
completion(true)
} else {
completion(false)
}
}
}
Then we can call this like so:
checkIfEmailExists(email: emailTextField.text!, completion: {(exists) in
if exists {
//Present error that the email is already used
} else {
//Segue to next view controller
}
})

Swift Firebase Check if user exists

What am i doing wrong? I have a database structure like the one shown in this image.
In appleDelegate.swift i just want to check if a certain user token actually exists under the "users" node. that is, if "users" has the child currentUserID (a string token). I understand observeSingleEvent is executed asynchronously.I get this error in swift: 'Application windows are expected to have a root view controller at the end of application launch'. in "func application(_ application: UIApplication" i have this code. I also have my completion handler function below.
if let user = Auth.auth().currentUser{
let currentUserID = user.uid
ifUserIsMember(userId:currentUserID){(exist)->() in
if exist == true{
print("user is member")
self.window?.rootViewController = CustomTabBarController()
} else {
self.window?.rootViewController = UINavigationController(rootViewController: LoginController())
}
}
return true
} else {
self.window?.rootViewController = UINavigationController(rootViewController: LoginController())
return true
}
}
func ifUserIsMember(userId:String,completionHandler:#escaping((_ exists : Bool)->Void)){
print("ifUserIsMember")
let ref = Database.database().reference()
ref.child("users").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild(userId) {
print("user exists")
completionHandler(true)
} else {
print("user doesn't exist")
completionHandler(false)
}
})
}
I would suggest moving the code out of the app delegate and into an initial viewController. From there establish if this is an existing user and send the user to the appropriate UI.
.observeSingleEvent loads all of the nodes at a given location - one use would be to iterate over them to populate a datasource. If there were 10,000 users they would all be loaded in if you observe the /users node.
In this case it's really not necessary. It would be better to just observe the single node you are interested in and if it exists, send the user to a UI for existing users.
here's the code to do that
if let user = Auth.auth().currentUser {
let ref = self.ref.child("users").child(user.uid)
ref.observeSingleEvent(of: .value, with: { snapshot in
self.presentUserViewController(existing: snapshot.exists() )
})
}
snapshot.exists will be either true if the user node exists or false if not so the function presentUserViewController would accept a bool to then set up the UI depending on the user type.

Firebase Login Persistence Swift

I'm using Firebase to handle my user register and login for my app. But if I log in, and then close my app entirely - the user is forced to re-log in. I'd like to keep the user logged in unless they click "Log out"
My login code is this:
Auth.auth().signIn(withEmail: email, password: password, completion: {(user, error) in
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
}
self.presentTabBar()
})
}
}
How do I keep this user logged in unless specifically told to logout?
Here's a handy full example for 2020:
Anywhere in your iOS+Firebase app, you can simply say:
guard let uid = Auth.auth().currentUser?.uid else {
return print("no current user!")
}
Thus, on the launch screen of your app, simply:
import UIKit
import Firebase
import FirebaseUI
class ViewController: UIViewController, FUIAuthDelegate {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let uid = Auth.auth().currentUser?.uid else {
return print("no current user, hence AuthUI flow...")
basicAuthUIFlow()
}
print("User .. \(Auth.auth().currentUser?.displayName)")
continueWhenUserPresent()
}
It's that easy. So then as usual ...
private func basicAuthUIFlow() {
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self
let pp: [FUIAuthProvider] = [ FUIGoogleAuth() ]
authUI?.providers = pp
if let authVC = authUI?.authViewController() {
present(authVC, animated: true)
}
}
func authUI(_ authUI: FUIAuth,
didSignInWith authDataResult: AuthDataResult?,url: URL?, error: Error?) {
let u = authDataResult?.user
print("Successful login via authUI \(String(describing: u?.displayName))")
continueWhenUserPresent()
}
private func continueWhenUserPresent() {
.. pushViewController .. your first screen
}
Check if the user is logged in or not:
if Auth.auth().currentUser != nil {
// User is signed in.
// ...
} else {
// No user is signed in.
// ...
}
if the user is logged in, then go the Home ViewController. This way when he opens the app again he will go to the Home ViewController unless he sign outsFIRAuth.auth().signOut()
For more info check this: https://firebase.google.com/docs/auth/ios/manage-users
For keep user login you need to check currentUser Auth session, If it's not nil then you can redirect user to Home screen.
Call "setRootViewController" method from didFinishLaunchingWithOptions, just after FirebaseApp.configure() code
Swift 4
func setRootViewController() {
if Auth.auth().currentUser != nil {
// Set Your home view controller Here as root View Controller
self.presentTabBar()
} else {
// Set you login view controller here as root view controller
}
}

Show View Controller based on the users Firebase login state

In my app I want do display different ViewControllers depending on if the user is already logged in or not. Like if the user is logged in present VC_A if not, present VC_B. I tried the following code in my vc_login:
if let user = FIRAuth.auth()?.currentUser {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "vc_home") as! ViewController_Home
self.present(vc, animated: true, completion: nil)
} else {
// Do Nothing
}
You can do it like that (hints are in the code comments):
if FIRAuth.auth()?.currentUser?.uid == nil {
// user is not logged in
// present VC_B
} else {
// user is logged in
// present VC_A
}
Or you can use the ternary conditional operator like this:
FIRAuth.auth()?.currentUser?.uid == nil ? presentViewControllerB() : presentViewControllerA()
func presentViewControllerA() {
// call if logged in
}
func presentViewControllerB() {
// call if not logged in
}

how to segue to 2nd page from successful login - "warning attempt to present on while a presentation is in progress" error

How do I segue to my 2nd page after successfully verifying login?
I have pulled a segue from the login page view controller (not the login button) to the next page and named the segue 'nextPage'. (If I segue from the login button then the button click allows all logins to segue through without testing them). When I segue from the login page it correctly checks details but does not segue to the next page on successful login, and instead I get the console error "Warning: Attempt to present on while a presentation is in progress!"
the code is
#IBAction func loginButtonTapped(sender: AnyObject) {
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
let userEmailStored = NSUserDefaults.standardUserDefaults().stringForKey("userEmail");
let userPasswordStored = NSUserDefaults.standardUserDefaults().stringForKey("userPassword");
if userEmailStored == userEmail && userPasswordStored == userPassword {
// Login successful
// Display an alert message
displayMyAlertMessage("Login successful. Thank you");
NSUserDefaults.standardUserDefaults().setBool(true,forKey:"isUserLoggedIn");
NSUserDefaults.standardUserDefaults().synchronize();
print("login success!")
self.dismissViewControllerAnimated(true, completion:nil);
self.performSegueWithIdentifier("nextPage", sender: self);
} else if userEmailStored != userEmail {
// Login unsuccessful (email incorrect)
NSUserDefaults.standardUserDefaults().setBool(false,forKey:"isUserLoggedIn");
print("login unsuccessful. Incorrect email.")
// Display an alert message
displayMyAlertMessage("Incorrect login details.");
return;
} else if userPasswordStored != userPassword {
// Login unsuccessful (password incorrect)
// Display an alert message
displayMyAlertMessage("Incorrect login details");
//return;
NSUserDefaults.standardUserDefaults().setBool(false,forKey:"isUserLoggedIn");
print("login unsuccessful. Incorrect password.")
}
The login page comes after an initial 'protected' login/logout screen as ViewController.swift with this code
override func viewDidAppear(animated: Bool)
{
let isUserLoggedIn = NSUserDefaults.standardUserDefaults().boolForKey("isUserLoggedIn");
if(!isUserLoggedIn)
{
self.performSegueWithIdentifier("loginView", sender: self);
}
}
#IBAction func logoutButtonTapped(sender: AnyObject) {
NSUserDefaults.standardUserDefaults().setBool(false,forKey:"isUserLoggedIn");
NSUserDefaults.standardUserDefaults().synchronize();
self.performSegueWithIdentifier("loginView", sender: self);
}
}
I do suggest to have a different approach on this.
If you set a storyboardID to LoginViewController you can directly manage to override the Protected page checking directly in AppDelegate.
For example you can try to do this
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
[...]
let isUserLoggedIn = NSUserDefaults.standardUserDefaults().boolForKey("isUserLoggedIn")
if isUserLoggedIn {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc = storyboard.instantiateViewControllerWithIdentifier("IDYOUSETBEFORE")
window.rootViewController = vc
return
}
I recently managed to release a pod in order to easily handle this situations, have a look at StoryboardEnum lib
I solved this by removing the alert controller function, i.e. the code
displayMyAlertMessage("Login successful. Thank you");
as this was segueing to the 'login successful' popup view controller, instead of the segue that I needed, and in effect blocking the next page, while also not really necessary, as successful login means moving to the next page. I was able to still keep the alert/ popups for 'incorrect login details' which were the only essential alerts.