Creating Firestore document with text field data - swift

So im making a sign up page on xcode with firebase and it currently work well but I would like to get more information from the user such as the first name and the last name . Basically I want my code to automatically create a document named with the email of the user that just sign up in the "Users" collection on firestore and after create the field "FirstName" and "LastName" with the text in those textfield! You can see my code below . Thanks for your help in advance. And I also provide a screenshot ( I did it manually to explain what I want it to do )
#IBOutlet weak var FirstName: UITextField!
#IBOutlet weak var LastName: UITextField!
#IBOutlet weak var Email: UITextField!
#IBOutlet weak var Password: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func SignUpButton(_ sender: Any) {
if Email.text?.isEmpty == true {
print("No text in email field")
return
}
if Password.text?.isEmpty == true {
print("No text in password field")
return
}
if FirstName.text?.isEmpty == true {
print("No text in first name field")
return
}
if LastName.text?.isEmpty == true {
print("No text in Last name field")
return
}
SignUp()
}
#IBAction func LoginButton(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "SignInPage")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
func SignUp() {
Auth.auth().createUser(withEmail: Email.text!, password: Password.text!) { (authResult, error) in
guard let user = authResult?.user, error == nil else {
print("Error \(error!.localizedDescription)")
return
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "Manager0")
vc.modalPresentationStyle = .overFullScreen
self.present(vc, animated: true)
}
}

You cannot get a user's first and last name as Firebase only honours the full name only via displayName.
Moreover, with email and password login, you only get the email - as Firebase would have no way to get the user's Name. (Suppose a user has Yahoo Email ID - how would it get the name of the user?)

Related

Trying to switch which photo pops up depending on button clicked Xcode

I am making an app that will display a random quote from a stoic philosopher. Right now, I am stuck on trying to make the correct picture pop up. (User clicks on a Button with the philosopher's name on it, and then a new view pops up with an image of the philosopher and a random quote by him).
class ViewController: UIViewController {
var allQuotes = [String]()
var pictures = [String]()
#IBOutlet var Epictetus: UIButton!
#IBOutlet var Seneca: UIButton!
#IBOutlet var MarcusAurelius: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create a constant fm and assign it the value returned by FileManager.default (built in system type)
let fm = FileManager.default
// Declares a new constant called path that sets the resource path of ours apps buddle.
// A bundle is a directory containing our compiled program and all our assets
let path = Bundle.main.resourcePath!
// items array will be a constant collection of the names of all the files found in the directory of our app
let items = try! fm.contentsOfDirectory(atPath: path)
// create a loop to go through all of our items...
for item in items {
if item.hasSuffix("jpg"){
pictures.append(item)
}
}
print(pictures)
title = "Stoicism"
if let stoicQuotesURL = Bundle.main.url(forResource: "quotes", withExtension: "txt"){
if let stoicQuotes = try? String(contentsOf: stoicQuotesURL) {
allQuotes = stoicQuotes.components(separatedBy: "\n\n")
}
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
}
That's the code for my main viewController.
import UIKit
class PictureViewController: UIViewController {
#IBOutlet var picture: UIImageView!
#IBOutlet var imageView: UIImageView!
var selectedImage: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let imageToLoad = selectedImage {
imageView.image = UIImage(named: imageToLoad)
}
}
override func viewWillAppear(_ animated: Bool) {
// doing it for the parent class
super.viewWillAppear(animated)
// if its a nav Cont then it will hide bars on tap...
}
// now make sure it turns off when you go back to the main screen
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
That's the code for the viewController that has the imageView. Right now, the image that's popping up is always the preset (Marcus Aurelius), even though my code looks correct to me. Obviously it isn't (also, I've already debugged and ensured through print statements that the jpg files add to the pictures array correctly).
Any help would be appreciated.
First of all, this code is really silly:
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
Do you see that everything in those lines is identical except for the numbers? So make the number a variable:
#IBAction func buttonTapped(_ sender: UIButton) {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
print(sender.tag)
vc.selectedImage = pictures[sender.tag]
navigationController?.pushViewController(vc, animated: true)
}
}
See how much shorter and clearer that is? Okay, I've also added a print statement. This will print the tag to the console. You need to make sure that your buttons do have the right tags. If they do, your code should work.

Sign In with Google Authentication

I've implemented Google Authentication via Firebase in my App. Everything works smoothly except for one small problem that I can't seem to find. Whenever the user opens the page that prompts them to "Sign in with Google" (ie. login page or sign up page), the banner appears momentarily before disappearing. I do not want it to appear at all, unless the user clicks the "Sign in with Google" button. How can I get rid of this?
WelcomeViewController (the view controller with the google login)
import UIKit
import FirebaseAuth
import Firebase
import FBSDKLoginKit
import GoogleSignIn
class WelcomeViewController: UIViewController, GIDSignInDelegate {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var signInFacebookButton: UIButton!
#IBOutlet weak var signInGoogleButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setUpGoogleButton()
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance().signIn()
}
// SIGN IN WITH GOOGLE
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let err = error {
print("Failed to log into Google: ", err)
return
}
print("Successfully logged into Google")
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential, completion: { (user, error) in
if let err = error {
print("Failed to create a Firebase User with Google account: ", err)
return
}
// Successfully logged in
guard let uid = user?.user.uid else { return }
print("Successfully logged into Firebase with Google", uid)
// switch to tab bar controller
let tabBarC = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") as! TabBarController
tabBarC.modalPresentationStyle = .fullScreen
self.present(tabBarC, animated: true, completion: nil)
print("Switched to TabBarController")
})
}
fileprivate func setUpGoogleButton() {
let button = signInGoogleButton
button?.layer.borderWidth = 0
button?.backgroundColor = UIColor.init(red: 130/255, green: 178/255, blue: 189/255, alpha: 1)
button?.layer.cornerRadius = 20.0
button?.tintColor = UIColor.white
button!.addTarget(self, action:
#selector(handleCustomGoogleSignIn), for: .touchUpInside)
GIDSignIn.sharedInstance()?.delegate = self
}
#objc func handleCustomGoogleSignIn() {
GIDSignIn.sharedInstance().signIn()
}
I've attached a link to a screen recording of what happens. The second page shown in the screen recording is identical to the code below, so it has the same problem. Any help is appreciated, thank you!
https://drive.google.com/file/d/1t4KV0Z6qwfCK56Gf2314wXWhAeQR0wUs/view?usp=sharing
That's because of your code inside viewDidLoad(). You are implementing this method:
GIDSignIn.sharedInstance().signIn()
This triggers the sign in method as soon as the view loads (as you are implementing it inside viewDidLoad()), and that causes that momentary sign in pop up that disappears.
Instead of implementing that method there, you should only implement it inside your handleCustomGoogleSignIn().
Conclusion, your viewDidLoad() should look like this:
override func viewDidLoad() {
super.viewDidLoad()
setUpGoogleButton()
GIDSignIn.sharedInstance()?.presentingViewController = self
}

Attempt to present <UIAlertController> on whose view is not in the window hierarchy

I've been trying to present a UIAlertController when user entered wrong password for their account, the UIAlertController is located in one separate file inside a Model group which I extend the UIViewController class to add this alert functionality to it. I also has another file inside my model group namely LogIn which I wrote all the logic behind the login process so that I can call it to my LogInVC. However, I got an error of "Attempt to present on whose view is not in the window hierarchy!" whenever the function get call inside my LogInVC. I'm trying to make my project in MVC and I know what caused this error but I just don't know how to fix it. May anyone tell me how to fix this problem?
Alert
import Foundation
import UIKit
extension UIViewController {
//MARK: - Not Enough Information Alert
func notEnoughInfo(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
//MARK: - Incorrect Username and Password
func wrongInfo(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "Try again", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
LogIn
import Foundation
import Firebase
class LogIn: UIViewController{
let db = Firestore.firestore()
//MARK: - userValidation()
func userValidation(Username:String, Password:String){
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapShotDocument = querySnapshot?.documents {
for doc in snapShotDocument {
let data = doc.data()
if let username = data[C.username] as? String, let password = data[C.password] as? String {
if Username == username, Password == password {
print("Log in Successfully")
}
else {
self.wrongInfo(title: "Incorrect password", message: "Try again please")
}
}
}
}
}
}
}
}
LogInVC
import UIKit
import Firebase
class LogInVC: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var logInBtn: UIButton!
let db = Firestore.firestore()
let logIn = LogIn()
override func viewDidLoad() {
super.viewDidLoad()
//logInBtn.layer.cornerRadius = logInBtn.frame.height/5
}
#IBAction func logInBtn(_ sender: UIButton) {
if let username = emailTextField.text, let password = passwordTextField.text{
if username.isEmpty || password.isEmpty{
notEnoughInfo(title: "Not enough information", message: "Please fill in all the necessary information.")
}else{
logIn.userValidation(Username: username, Password: password) //here is where problem occured
//move to another viewcontroller
}
}
}
#IBAction func signUpBtn(_ sender: UIButton) {
let push = storyboard?.instantiateViewController(withIdentifier: C.signUpVC) as! SignUpVC
push.modalPresentationStyle = .fullScreen
present(push, animated: true, completion: nil)
}
} //ends of class
You need to first dismiss the current present alert or present controller. currently you are trying to present controller over a controller that's why it shows this error. Don't present . remove this line from self.wrongInfo(title: "Incorrect password", message: "Try again please") from LogIn.
try this and you can comment again if there is anything regarding this.

Swift: How to check if user's phone number is already in Firebase database before creating a new user

I just started working with Swift a few months ago and to help me learn the language better, I am creating a chat application. For the sign-in method, I am using the phone number method. I have the onboarding process already created but I want to implement something that lets me check if the user has already created an account with that phone number. If they have, I want to segue them to the main view controller, skipping the onboarding view controller.
Here is my code for the phone verification view controllers (One is for inputting a phone number and the other is for inputting the code sent to the user's phone):
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class PhoneVerification: UIViewController {
//MARK: Properties
#IBOutlet weak var phoneNumber: UITextField!
#IBOutlet weak var code: UITextField!
#IBOutlet weak var verifyCodeImage: UIButton!
#IBOutlet weak var sendCodeImage: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: Format phone text field
var phoneFormatter = PhoneNumberFormatter()
#IBAction func formatPhoneNumber(_ sender: UITextField) {
sender.text = phoneFormatter.format(sender.text!, hash: sender.hash)
}
//MARK: When send secret code button is pressed
#IBAction func sendCode(_ sender: Any) {
let submitPhoneNumber = "+1" + phoneNumber.text!
if submitPhoneNumber.count > 9{
PhoneAuthProvider.provider().verifyPhoneNumber(submitPhoneNumber, uiDelegate: nil) {(verificationID, error) in
if error != nil {
print(error!)
}else{
UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
self.performSegue(withIdentifier: "phoneCode", sender: self)
}
}
}else{
let phoneNumAlert = UIAlertController(title: "Please enter your phone number", message: "You must enter your phone number to continue.", preferredStyle: .alert)
phoneNumAlert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
self.present(phoneNumAlert, animated: true)
}
}
let verificationID = UserDefaults.standard.string(forKey: "authVerificationID")
//MARK: When verify code button is pressed
#IBAction func verifyCode(_ sender: Any) {
let credential = PhoneAuthProvider.provider().credential(
withVerificationID: verificationID!,
verificationCode: code.text!)
//This is where the user is signed in if the verification code is correct
Auth.auth().signIn(with: credential) { (user, error) in
if let error = error {
let invalidCodeAlert = UIAlertController(title: "That code is incorrect", message: "Please input the correct code", preferredStyle: .alert)
invalidCodeAlert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
self.present(invalidCodeAlert, animated: true)
print(error)
return
}
//MARK: User is signed in
print("Phone number: \(String(describing: user?.phoneNumber))")
self.performSegue(withIdentifier: "accountCreated", sender: self)
}
}
}
All help is very much appreciated! Thanks!
Let's say you hold user data at the users ref. When the user signs in check to see if they have any data there, if they do then they are an existing user, if they don't then it is a new account:
Auth.auth().signIn(with: credential, completion: { [weak self](authDataResult, error) in
if let error = error { return }
guard let safeAuthDataResult = authDataResult else { return }
// 1. get the signed in user's userId
let userId = safeAuthDataResult.user.uid
// 2. check to see if their userId exists at the user's path
let usersRef = Database.database().reference().child("users").child(userId)
usersPublicDataRef.observeSingleEvent(of: .value, with: { (snapshot) in
// 3. this is a new user
if !snapshot.exists() {
// *** what you should do here is update the user's ref with some sort of data
} else {
// 4. this is an existing user
}
})
})
You can use FirebaseAuthUI and FirebasePhoneAuthUI to registered your mobile number into firebase. It's default method and UI which is provided by Firebase itself. So you don't have to worry about rest of things.
You just have to installed PhoneAuthUI using pod and write down below code to registered mobile number:
FUIAuth.defaultAuthUI()?.delegate = self
let phoneProvider = FUIPhoneAuth.init(authUI: FUIAuth.defaultAuthUI()!)
FUIAuth.defaultAuthUI()?.providers = [phoneProvider]
let currentlyVisibleController = self.navigationController?.visibleViewController
phoneProvider.signIn(withPresenting: currentlyVisibleController!, phoneNumber: nil)
Once your mobile number is registered then you will get a callback on this method:
func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
if user != nil{
// here we need to check if current user is registered or not.
var ref: DatabaseReference!
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
}) { (error) in
print(error.localizedDescription)
}
}else if error != nil{
print(error?.localizedDescription)
}
}
For more information, you can see this tutorial.

performSegueWithIdentifier causes crash

I'm setting up a sign up/login page using framework PARSE on XCode 6.
When I try to perform a segue (it is spelled correcty), hover, the app crash, even though the segue is inside an if statement.
Here's the code:
import UIKit
import Parse
class ViewController: UIViewController, UINavigationControllerDelegate{
var signUpMode = false
func displayAlert(title:String, message:String){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
//Outlet and actions
#IBOutlet var username: customTextField!
#IBOutlet var email: customTextField!
#IBOutlet var password: customTextField!
//Need the outlets for changes betweeen signUp and logIn modes!!!
#IBAction func signUp(sender: AnyObject) {
if signUpMode == true {
var user = PFUser()
user.username = username.text
user.password = password.text
user.email = email.text
// other fields can be set just like with PFObject
//user["phone"] = "415-392-0202"
user.signUpInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
// Hooray! Let them use the app now.
} else {
println("error")
self.displayAlert("Username already in use", message: "Please use another username")
}
}
}
else {
PFUser.logInWithUsernameInBackground(email.text, password:password.text) {
(user: PFUser!, error: NSError!) -> Void in
if user != nil {
self.displayAlert("You're in", message: "And you'll be successful")
self.performSegueWithIdentifier("goToPost", sender: self)
} else {
self.displayAlert("Wrong username or password", message: "Please try again")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
if signUpMode == false {
self.username.hidden = true
self.email.placeholder = "username"
}
}
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() != nil {
performSegueWithIdentifier("goToPost", sender: self)
}
}
}
The segue is inside the viewWillAppear method.
PFUser().currentUser() stores information about the current logged in user, so it's nil if no user is logged in.
Can you find out why it crashes?
I tried to put the segue inside viewDidLoad, but nothing else, it didn't even crashed.
Try segueing in viewDidAppear: and check if your segue identifier matches the one on your storyboard.