So the problem seems to be with my profileViewController. I set the current user after I am done signing up in this snippet of code.
class func setCurrent(_ user: User, writeToUserDefaults: Bool = true) {
print(user)
print("")
if writeToUserDefaults {
let data = NSKeyedArchiver.archivedData(withRootObject: user)
UserDefaults.standard.set(data, forKey: "currentUser")
UserDefaults.standard.synchronize()
}
_current = user
print(_current)
}
Then After That it goes to my profileViewController and tries to look for the user which turns out to be null. Why would it go there before I even went to the viewcontroller. Why is it nil?
Below is my profile view controller and my viewDidLoad
class ProfileeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var profileHandle: DatabaseHandle = 0
var profileRef: DatabaseReference?
let cellID = "cellID"
let profileSetupTransition = AlterProfileViewController()
let settingView = SettingsViewController()
var userEvents = [Event]()
var userId: String?
var user: User?
var emptyLabel: UILabel?
var currentUserName: String = ""
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
let user = self.user ?? User.current
profileHandle = UserService.observeProfile(for: user) { [unowned self](ref, user, events) in
self.profileRef = ref
self.user = user
self.userEvents = events
// self.jobs = allJobs
// self.reciepts = allReciepts
// print(self.userEvents)
// print(self.reciepts)
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
The method where I call setCurrent is below
// will handle the sign up of a user
#objc func handleSignUp(){
// first we cant to take sure that all of the fields are filled
var profilePic: String = ""
// will take the user selected image and load it to firebase
let imageName = NSUUID().uuidString
guard let username = self.nameTextField.text,
let confirmPassword = self.confirmPasswordTextField.text,
let email = self.emailTextField.text,
let password = self.passwordTextField.text,
!username.isEmpty,
!email.isEmpty,
!password.isEmpty,
!confirmPassword.isEmpty
else {
print("Required fields are not all filled!")
return
}
if self.validateEmail(enteredEmail:email) != true{
let alertController = UIAlertController(title: "Error", message: "Please Enter A Valid Email", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
// will make sure user is validated before it even tries to create user
// will make sure the password and confirm password textfields have the same value if so it will print an error
if self.passwordTextField.text != self.confirmPasswordTextField.text {
let alertController = UIAlertController(title: "Error", message: "Passwords Don't Match", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
//create a reference to the sotrage database in firebase
let storageRef = Storage.storage().reference().child("profile_images").child("\(imageName).PNG")
//following function does the work of putting it into firebase
//notice I also set the value of profilepic oo it can be saved in the updated user instance in the database
if let userImage = selectedImageFromPicker,let uploadData = UIImageJPEGRepresentation(userImage, 0.1){
AuthService.createUser(controller: self, email: email, password: password) { (authUser) in
guard let firUser = authUser else{
return
}
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(error ?? "")
return
}
profilePic = (metadata?.downloadURL()!.absoluteString)!
//printing to make sure values are contained in these strings
print(profilePic)
print(username)
UserService.create(firUser, username: username, profilePic: profilePic, location: self.userLocation!, completion: { (user) in
guard let user = user else {
print("User not loaded into firebase db")
return
}
User.setCurrent(user, writeToUserDefaults: true)
// will set the current user for userdefaults to work
print(user.profilePic ?? "")
print(user.username ?? "")
// self.delegate?.finishSigningUp()
self.finishSigningUp()
})
})
}
}
}
Then this method is called to move the user to the homeviewController and set it as root
func finishSigningUp() {
print("Finish signing up from signup view controller")
print("Attempting to return to root view controller")
let homeController = HomeViewController()
//should change the root view controller to the homecontroller when done signing up
self.view.window?.rootViewController = homeController
self.view.window?.makeKeyAndVisible()
}
When you do User.current is going to retrieve on the user default the current user that you previously saved on the user defaults on your setCurrent method right ?
Did you check if the user is correctly saved on the User defaults with the key "currentUser" ? Maybe that's why it's nil.
Why would it go there before I even went to the viewcontroller ? Maybe you are doing some kinf of asynchronous method computation and even when it is not finished, you move to the vie controller. You move to it without the result of your asynchronous method and it's logic that your values are nil because you don't let them to be set. That's an hypothesis as I see your piece of code and you call some asynchronous methods on your code flow.
Related
I am trying to create an app that requires the user to successfully enter a pin before being allowed onto the rest of the app.
I did some searching around and found a basic existing coredata example app that works here.
I went into the xcdatamodel and deleted their attributes and replaced with "pin" which is a String. Here is a screenshot of the xcdatamodel.
Then I modified the ViewController so that the createData UIbutton opens a alertController that prompts the user to enter a new pin twice, checks they are the same, and if they are it creates a coredata entry with that pin.
Here is the relevant code of the ViewController:
import UIKit
import CoreData
class ViewController: UIViewController {
var firstPinNumber:String = ""
var secondPinNumber:String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func createData(_ sender: Any) {
let enterPinAlertController = UIAlertController(title: "Enter New PIN", message: "", preferredStyle: .alert)
enterPinAlertController.addTextField{ (textField1:UITextField)->Void in
textField1.placeholder = "Enter PIN"
textField1.isSecureTextEntry = true
}
enterPinAlertController.addTextField{ (textField2:UITextField)->Void in
textField2.placeholder = "Re-Enter PIN"
textField2.isSecureTextEntry = true
}
let okAction = UIAlertAction(title: "OK", style: .cancel) {(action) in
if let textFields = enterPinAlertController.textFields {
let theTextFields = textFields as [UITextField]
self.firstPinNumber = theTextFields[0].text!
self.secondPinNumber = theTextFields[1].text!
if self.firstPinNumber != self.secondPinNumber {
print ("PINs dont match!")
let pinsDontMatchAlertController = UIAlertController(title: "PINs don't match!", message: "Try again", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .cancel) {(action) in
}
pinsDontMatchAlertController.addAction(okAction)
self.present(pinsDontMatchAlertController, animated: true, completion: nil)
}
}
}
enterPinAlertController.addAction(okAction)
self.present(enterPinAlertController, animated: true, completion: nil)
createPIN(pinNum: secondPinNumber)
}
func createPIN(pinNum: String){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Now let’s create an entity and new user records.
let userEntity = NSEntityDescription.entity(forEntityName: "User", in: managedContext)!
let user = NSManagedObject(entity: userEntity, insertInto: managedContext)
user.setValue(pinNum, forKeyPath: "pin")
print(user.value(forKey: "pin") as Any)
//Now we have set the pin. The next step is to save it inside the Core Data
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
#IBAction func retrieveData(_ sender: Any) {
let storedPin = retrievePIN()
print(storedPin)
}
func retrievePIN()->String {
var storedPin:String = ""
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return "" }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Prepare the request of type NSFetchRequest for the entity
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "User")
fetchRequest.fetchLimit = 1
// fetchRequest.predicate = NSPredicate(format: "username = %#", "Ankur")
// fetchRequest.sortDescriptors = [NSSortDescriptor.init(key: "email", ascending: false)]
//
do {
let result = try managedContext.fetch(fetchRequest)
for data in result as! [NSManagedObject] {
if data.value(forKey: "pin") != nil {
storedPin = data.value(forKey: "pin") as! String
print(storedPin)
} else {
print ("Found nil")
}
}
} catch {
print("Failed")
}
return storedPin
}
Using breakpoints I have ascertained that it enters the createPin() function, but it seems to enter that function BEFORE it presents the enterPinAlertController to enter the new pin, even though createPin() is called AFTER the enterPinAlertController is presented.
Also if I use the retrieveData UIButton it prints out "Found nil"
So if what I'm thinking is correct, its creating a coredata entry with an empty string, or nothing at all?
How can I fix this so that it creates a coredata entry with the string the user enters as the new pin, and also retrieves it later?
Your call to createPin needs to be inside the action handler for okAction. As you have it now, secondPinNumber will be called before the alert has been shown, so it will be empty or nil, depending on how you initialise it.
IBAction func createData(_ sender: Any) {
let enterPinAlertController = UIAlertController(title: "Enter New PIN", message: "", preferredStyle: .alert)
enterPinAlertController.addTextField{ (textField1:UITextField)->Void in
textField1.placeholder = "Enter PIN"
textField1.isSecureTextEntry = true
}
enterPinAlertController.addTextField{ (textField2:UITextField)->Void in
textField2.placeholder = "Re-Enter PIN"
textField2.isSecureTextEntry = true
}
let okAction = UIAlertAction(title: "OK", style: .cancel) {(action) in
if let textFields = enterPinAlertController.textFields,
let firstPinNumber = textFields[0].text,
let secondPinNumber = textFields[1].text,
firstPinNumber == secondPinNumber {
createPIN(pinNum: secondPinNumber)
} else {
print ("PINs dont match!")
let pinsDontMatchAlertController = UIAlertController(title: "PINs don't match!", message: "Try again", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .cancel)
pinsDontMatchAlertController.addAction(okAction)
self.present(pinsDontMatchAlertController, animated: true, completion: nil)
}
}
}
enterPinAlertController.addAction(okAction)
self.present(enterPinAlertController, animated: true, completion: nil)
}
I've been having an issue with a personal app where certain users seem to be missing a profile picture when they sign up (String). I've added a check to make sure the string isn't empty when the user presses the "Next" button - basically check if the string is "", and if so, present an alert controller to remind them to choose a profile picture. If it's not empty, then segue to the next screen. This is the relevant code (for clarity, var emailUserPicString = "" is a global variable):
emailUserPicString = url.absoluteString
print("\n\n\npic:\(emailUserPicString)\n\n\n")
if emailUserPicString == "" {
let alertController = UIAlertController(title: "Profile Picture Error", message: "Don't forget to choose a profile picture!", preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
return
} else {
self.performSegue(withIdentifier: "emailToSetup", sender: nil)
}
Now, when I test this solution, pressing the Next button doesn't do anything, and no alert is presented. I'd think it would be one or the other - if the string is empty, the alert controller would be shown, and if there's a value, performSegue would happen and we'd be taken to the next screen. I'm wondering why neither of these are happening
This is the complete function if it can provide some context:
#IBAction func emailSignupNextPressed(_ sender: Any) {
// Make sure text fields aren't empty
guard nameField.text != "", emailField.text != "", passwordField.text != "", confirmPasswordField.text != "" else {return}
if passwordField.text == confirmPasswordField.text {
Auth.auth().createUser(withEmail: emailField.text!, password: passwordField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
}
if let user = user {
guard let uid = Auth.auth().currentUser?.uid else {return}
// Use name as Firebase display name for readability
let changeRequest = Auth.auth().currentUser!.createProfileChangeRequest()
changeRequest.displayName = self.nameField.text!
changeRequest.commitChanges(completion: nil)
// Create child node from userStorage "users". Profile image set to user's unique ID
let imageRef = self.userStorage.child("\(uid).jpg")
let data = UIImageJPEGRepresentation(self.selectProfileImageView.image!, 0.5)
// Upload image to Firebase
let uploadTask = imageRef.putData(data!, metadata: nil, completion: { (metadata, err) in
if err != nil {
print(err!.localizedDescription)
}
imageRef.downloadURL(completion: { (url, er) in
if er != nil {
print(er?.localizedDescription as Any)
}
if let url = url {
emailUserPicString = url.absoluteString
print("\n\n\npic:\(emailUserPicString)\n\n\n")
if emailUserPicString == "" {
let alertController = UIAlertController(title: "Profile Picture Error", message: "Don't forget to choose a profile picture!", preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
return
} else {
self.performSegue(withIdentifier: "emailToSetup", sender: nil)
}
}
})
})
uploadTask.resume()
}
})
} else {
// print("Passwords don't match")
passwordAlert()
}
}
My question is, am I handling the string check correctly? If there's no value there, why is my alert controller not presenting? And if there is a value why is the segue not being performed?
As I can see, you are presenting the UIAlertAction in a background Thread. So, you should present it in the UI Thread using:
DispatchQueue.main.async {
// show alert here
}
try to change your if statement to
if emailUserPicString.isEmpty || emailUserPicString.isEmpty == ""{
}
Maybe an empty textField don't has a == "" String
Follow this solution:
private func validateForm() {
if emailTextField.text?.isEmpty {
// Show alert message like: Please enter the email addess.
return
}
if passwordTextField.text?.isEmpty && passwordTextField.text?.count < 6 {
// Show alert message like: Password must be at least 6 chars long.
return
}
if profleImage.image == nil {
// Show alert message like: Please choose your profile photo.
return
}
// Now you can post API request after validating all values.
// Call here API request method.....
}
Whenever user signs in/up to the app, some Firebase observations, such as fetchSavedActionsForTracking() and fetchActionsForDowns() (see the full functions below), do not observe during clicking buttons and the app acts weird like mixing up with nodes in Firebase. As soon I compile the code again, the code works perfect and everything returns normal. I am not sure if Xcode causes this issue or I have many observations in View Controller. If someone experience the same problem or have an idea, please let me know. I have also couple of observations in other VC but they are fine
Here is the observations I have I'm View Controller:
func fetchProperties() {
// Added the first property
Database.database().reference().child("Properties").observe(.childAdded, with: { (snapshot) in
if let dictinaryProperty = snapshot.value as? [String: AnyObject]{
// Assign property to Property()
self.property = Property()
// set the variables in dictionaryProperty's value to property's value (Be carefull the names have to match)
self.property?.setValuesForKeys(dictinaryProperty)
// User ID in Firebase
self.property?.keyID = snapshot.key
// Then add it to arrayPropertys
self.arrayPropertys.append(self.property!)
// This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
// Remove
Database.database().reference().child("Properties").observe(.childRemoved, with: { (snapshot) in
if let dictinaryProperty = snapshot.value as? [String: AnyObject]{
// Assign property to Property()
self.property = Property()
// set the variables in dictionaryProperty's value to property's value (Be carefull the names have to match)
self.property?.setValuesForKeys(dictinaryProperty)
// User ID in Firebase
self.property?.keyID = snapshot.key
// Check on the loaded tracking
if self.arrayPropertysTracking.isEmpty != true {
// Then delete from Firebase
self.checkingAndDeletingTracking(remove: self.property!)
}
// Then add it to arrayPropertys
self.arrayPropertysRemoved.append(self.property!)
self.removedProperties()
// This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
// Child is edited
Database.database().reference().child("Properties").observe(.childChanged, with: { (snapshot) in
if let dictinaryProperty = snapshot.value as? [String: AnyObject]{
// Assign property to Property()
self.property = Property()
// set the variables in dictionaryProperty's value to property's value (Be carefull the names have to match)
self.property?.setValuesForKeys(dictinaryProperty)
// User ID in Firebase
self.property?.keyID = snapshot.key
// Check if the updated property exists in Tracking window and if it does update it too
if self.arrayPropertysTracking.isEmpty != true {
self.checkingAndUpdateTracking(update: self.property!)
}
// Then add it to arrayPropertys
self.arrayPropertiesEdited.append(self.property!)
self.editedProperties()
// This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
Second;
// Load saved actions of the current user
func fetchSavedActionsForTracking() {
let userID = Auth.auth().currentUser?.uid
if let actualUserID = userID {
// Add the first property
Database.database().reference().child("Actions For Tracking").child(actualUserID).observe(.childAdded, with: { (snapshot) in
// The key is the property owner's ID
let keySnapshot = snapshot.key as? String
// Tags of saved buttons
let valueSnapshot = snapshot.value as? String
if let actualKey = keySnapshot {
self.arraySavedKeyIDs.append(actualKey)
}
//This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}, withCancel: nil)
// Removed the saved actions in Firebase when a property delected in Tracking window
Database.database().reference().child("Actions For Tracking").child(actualUserID).observe(.childRemoved, with: { (snapshot) in
// The key is the property owner's ID
let keySnapshot = snapshot.key as? String
// Tags of saved buttons
let valueSnapshot = snapshot.value as? String
if let actualKey = keySnapshot {
self.arrayRemovedTags.append(actualKey)
}
// Remove the tag from the array
self.removedArrayTags()
// When saved is clicked twice clear the arrayRemovedTags
self.arrayRemovedTags.removeAll()
//This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}, withCancel: nil)
}
}
Third;
// Load properties from tracking window
func fetchTracking() {
let userID = Auth.auth().currentUser?.uid
if let actualUserID = userID {
Database.database().reference().child("Tracking").child(actualUserID).observe(.childAdded, with: { (snapshot) in
if let dictinaryProperty = snapshot.value as? [String: AnyObject]{
// Assign property to Property()
self.property = Property()
// set the variables in dictionaryProperty's value to property's value (Be carefull the names have to match)
self.property?.setValuesForKeys(dictinaryProperty)
// Then add it to arrayPropertys
self.arrayPropertysTracking.append(self.property!)
// This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
// Remove
Database.database().reference().child("Tracking").child(actualUserID).observe(.childRemoved, with: { (snapshot) in
if let dictinaryProperty = snapshot.value as? [String: AnyObject]{
// Assign property to Property()
self.property = Property()
// set the variables in dictionaryProperty's value to property's value (Be carefull the names have to match)
self.property?.setValuesForKeys(dictinaryProperty)
// Then add it to arrayPropertys
//self.arrayRemovedTracking.append(self.property!)
// Remove the property from arrayPropertysTracking
for remove in 0...self.arrayPropertysTracking.count-1 {
if self.arrayPropertysTracking[remove].keyID == self.property?.keyID {
self.arrayPropertysTracking.remove(at: remove)
return
}
}
// This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
}
Forth:
// Fetch the down button actions
func fetchActionsForDowns() {
let userID = Auth.auth().currentUser?.uid
if let actualUserID = userID {
// Add the first property
Database.database().reference().child("Actions For Downs").child(actualUserID).observe(.childAdded, with: { (snapshot) in
// The key is the property owner's ID
let keySnapshot = snapshot.key as? String
// Tags of saved buttons
let valueSnapshot = snapshot.value as? String
if let actualKey = keySnapshot {
self.arrayDownsKeyIDs.append(actualKey)
}
//This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}, withCancel: nil)
// Removed the saved actions in Firebase when a property delected in Tracking window
Database.database().reference().child("Actions For Downs").child(actualUserID).observe(.childRemoved, with: { (snapshot) in
// The key is the property owner's ID
let keySnapshot = snapshot.key as? String
// Tags of saved buttons
// let valueSnapshot = snapshot.value as? String
if let actualKey = keySnapshot {
self.arrayDownsRemoved.append(actualKey)
}
// Remove the action from the array
self.removeActionsForDownsFunctions()
// When saved is clicked twice clear the arrayRemovedTags
self.arrayDownsRemoved.removeAll()
//This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}, withCancel: nil)
}
}
Fifth;
func fetchAnsweresOnDowns() {
let userID = Auth.auth().currentUser?.uid
if let actualUserID = userID {
Database.database().reference().child("Answers for Downs").child(actualUserID).observe(.childAdded, with: { (snapshot) in
if let dictinaryProperty = snapshot.value as? [String: AnyObject]{
// this will empty the variables in downedProperty
self.answerDowns = Property()
// set the variables in dictionaryProperty's value to property's value (Be carefull the names have to match)
self.answerDowns?.setValuesForKeys(dictinaryProperty)
// Type
self.answerDowns?.downID = snapshot.key
// Then add it to arrayPropertys
self.arrayAnswerDowns.append(self.answerDowns!)
// Do not upload unless cell has been clicked
if self.isExpanded == true {
// Upload into received downs
self.uploadeReceivedDowns(receive:self.arrayPropertys[(self.selectedIndex?.row)!])
}
// This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
// Removed property from answers on downs
Database.database().reference().child("Answers for Downs").child(actualUserID).observe(.childRemoved, with: { (snapshot) in
if let dictinaryProperty = snapshot.value as? [String: AnyObject]{
// this will empty the variables in downedProperty
self.answerDowns = Property()
// set the variables in dictionaryProperty's value to property's value (Be carefull the names have to match)
self.answerDowns?.setValuesForKeys(dictinaryProperty)
// Type
self.answerDowns?.downID = snapshot.key
// Then add it to arrayPropertys
self.arrayRemovedAnswerDowns.append(self.answerDowns!)
self.removeAnswerDownsFunction()
// This will crash because of background thread, so lets use DispatchQueue.main.async
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
}
Here the viewDidLoad() function for the main view controller:
verride func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// User is not logged in
checkIfUserIsLogedin()
// The delegate for receiving an action/data and notifying the viewcontroller
// The datasource is to feed an object such as a tableview and piker from the viewcontroller
tableView.delegate = self // Notify the ViewController whenever the user tabs on rows or scroll
tableView.dataSource = self // The dataSource will ask the ViewController for table cells
// Initialize ref
ref = Database.database().reference()
// Creates UINib documents(i.e it create the the files created by main.storyboard or simailiar and truning them to a file)
let nib = UINib.init(nibName: "CustomTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "cell")
// Set up the search bar
searchBarSetup()
// This function has to be here to reload table view with added property (i.e refresh table)
fetchProperties()
fetchSavedActionsForTracking()
fetchTracking()
fetchActionsForDowns()
fetchAnsweresOnDowns()
}
and here is where I check if the user is logged in or not (Also exists in the main view controller):
func checkIfUserIsLogedin() {
if Auth.auth().currentUser?.uid == nil {
perform(#selector(handleLogout), with: nil, afterDelay: 0)
//handleLogout()
}
}
func handleLogout() {
// When User click logout
do {
try Auth.auth().signOut()
} catch let logoutError {
print(logoutError)
}
// Object of loginViewController
loginVC = storyboard?.instantiateViewController(withIdentifier: "LoginVC")
// Transition to loginViewController()
present(loginVC!, animated: true, completion: nil)
}
Here is the authentication (I have it in separate view controller)
Sign in:
// Sign in into Firebase
func handleLogin() {
// Unwrap the text fields using guard which will simply do if-statament
guard let email = emailTextField.text, let password = passwordTextField.text else {
print("is nil")
return
}
Auth.auth().signIn(withEmail: email, password: password) { (user, err) in
if err != nil {
// There is an error
if email == "" || password == "" {
print("Empty")
// Create an alert message
let alertMessage = UIAlertController(title: "Empty Fields", message: "Please Fill The Fields", preferredStyle: .alert)
// Attach an action on alert message
alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
alertMessage.dismiss(animated: true, completion: nil)
}))
// Display the alert message
self.present(alertMessage, animated: true, completion: nil)
return
}
print("Wrong Email or Password")
// Create an alert message
let alertMessage = UIAlertController(title: "Wrong Email or Password", message: "Please try again", preferredStyle: .alert)
// Attach an action on alert message
alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
alertMessage.dismiss(animated: true, completion: nil)
}))
// Display the alert message
self.present(alertMessage, animated: true, completion: nil)
return
}
// Sueccessful
self.presentingViewController?.dismiss(animated: true, completion: nil)
print("Login sueccessful into Firebase database")
}
}
Registering:
func handleRegister() {
// Unwrap the text fields using guard which will simply do if-statament
guard let email = emailTextField.text, let password = passwordTextField.text, let name = nameTextField.text else {
print("is nil")
return
}
// Upload the user's name and email into authentication
Auth.auth().createUser(withEmail: email, password: password) { (user: User?, error) in
if error != nil {
// Handle the error (i.e notify the user of the error)
if email == "" || password == "" || name == "" {
print("Empty")
// Create an alert message
let alertMessage = UIAlertController(title: "Empty Fields", message: "Please Fill The Fields", preferredStyle: .alert)
// Attach an action on alert message
alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
alertMessage.dismiss(animated: true, completion: nil)
}))
// Display the alert message
self.present(alertMessage, animated: true, completion: nil)
return
}
else if let errCode = AuthErrorCode(rawValue: error!._code) {
switch errCode {
case .invalidEmail:
print("invalid email")
// Create an alert message
let alertMessage = UIAlertController(title: "Invalid Email", message: "Please check the entered email address", preferredStyle: .alert)
// Attach an action on alert message
alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
alertMessage.dismiss(animated: true, completion: nil)
}))
// Display the alert message
self.present(alertMessage, animated: true, completion: nil)
case .emailAlreadyInUse:
print("in use")
// Create an alert message
let alertMessage = UIAlertController(title: "Existed Email", message: "The email existed in our database, login instead of registering", preferredStyle: .alert)
// Attach an action on alert message
alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
alertMessage.dismiss(animated: true, completion: nil)
}))
// Display the alert message
self.present(alertMessage, animated: true, completion: nil)
case .weakPassword:
print("password is weak")
// Create an alert message
let alertMessage = UIAlertController(title: "Password is weak", message: "Use upper and lower characters along with numbers", preferredStyle: .alert)
// Attach an action on alert message
alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
alertMessage.dismiss(animated: true, completion: nil)
}))
// Display the alert message
self.present(alertMessage, animated: true, completion: nil)
default:
print("Other error!")
}
}
}
// Get the user id from Firebase
guard let uid = user?.uid else {
return
}
// Successfully authenticated
//Upload the name and user's ID into Database
let usersReference = self.ref?.child("Users Info").child(uid)
let values = ["name": name, "email": email]
//self.ref?.child("User1").setValue(values)
// Will add valuse without overwriting
usersReference?.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err)
return
}
// Transition to home screen
self.presentingViewController?.dismiss(animated: true, completion: nil)
print("Saved user successfully into Firebase database")
})
}
}
Here is the viewDidload() in the logging View controller:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// initialize the constraints of the white view and stack view
containerView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
nameTextField.translatesAutoresizingMaskIntoConstraints = false
emailTextField.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
// Create a varaible ref for Firebase
ref = Database.database().reference()
// You have to add the text field delegate if you want to add fuctions when user press/finish typing. For example when "return" key press, hide the keyboard
self.nameTextField.delegate = self
self.emailTextField.delegate = self
self.passwordTextField.delegate = self
in viewDidLoad(), the code checks if the user is logged in, but then calls
fetchProperties()
fetchSavedActionsForTracking()
fetchTracking()
fetchActionsForDowns()
fetchAnsweresOnDowns()
whether or not the user is logged in. If the user is logged out, then presumably you need to press some button that calls handleLogin(). But this doesn't call those five functions again, so the data isn't updated to reflect the current user. Essentially, you want to make sure those functions are called after a user is logged in. There are lots of different ways you could achieve this, but for example:
Call functions if user is logged in upon loading app
func checkIfUserIsLogedin() {
if Auth.auth().currentUser?.uid == nil {
perform(#selector(handleLogout), with: nil, afterDelay: 0)
//handleLogout()
} else {
fetchProperties()
fetchSavedActionsForTracking()
fetchTracking()
fetchActionsForDowns()
fetchAnsweresOnDowns()
}
}
And then call functions when user logs in if they weren't originally logged in
func handleLogin() {
...
...
// Successful
fetchProperties()
fetchSavedActionsForTracking()
fetchTracking()
fetchActionsForDowns()
fetchAnsweresOnDowns()
}
After looking on logging View Controller I found that I am dismissing the logging view controller. What solves my problem is the segue (Which I am not 100% sure why.) I made segue from logging view controller to tab bar view controller and works beautifully!
I would appreciate any explanations on why the segue solves this problem.
Thanks Jen for your help!
I have a login page in my app. The user enters their username and their password. I have an API that tells me if the username and password are correct and the user's id if they are. If they are not correct it shows a UIAlertView() that asks if you would like to create an account. The view has two buttons. A "No" button which dismisses the view and a "Yes" button which is supposed to contact an API to create the user's account. I have created alert actions before but it will not work with the code I have below. If you wouldn't mind could you please take a look and help me diagnose the problem?
//
// File.swift
// Reading Logs
//
// Created by Andrew on 12/8/15.
// Copyright © 2015 Wilson Apps for Education. All rights reserved.
//
import UIKit
class UserLogin {
var loginAlert = UIAlertView()
var user: String = ""
var pass: String = ""
func checkLogin() -> Bool{
let defaults = NSUserDefaults.standardUserDefaults()
let stat = defaults.valueForKey("loggedIn")
if(String(stat!) == "0"){
return false
}else{
return true
}
}
func logout(){
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue("0", forKey: "loggedIn")
defaults.setValue("", forKey: "logKey")
defaults.setValue("0", forKey: "userKey")
}
func login(username username: String, password: String, completion: (result: String) -> Void){
self.user = username
self.pass = password
let url = "http://www.wilsonfamily5.org/rlog/checkLogin.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if(contents as String == "0"){
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:nil, cancelButtonTitle: "No")
self.loginAlert.addButtonWithTitle("Yes")
self.loginAlert.show()
}else{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
completion(result: "1")
}
})
}.resume()
}
func register(username: String, password: String){
let url = "http://www.wilsonfamily5.org/rlog/newAccount.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
})
}.resume()
}
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
print("ButtonClicked")
if(buttonIndex == 1){
print("1ButtonClicked")
register(user, password: pass)
}
}
}
Step-1
Add UIAlertViewDelegate to your class;
class UserLogin, UIAlertViewDelegate {
....
Step-2
Set delegate and implement "Yes" button loginAlert object;
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate: self, cancelButtonTitle: "No", otherButtonTitles: "Yes")
self.loginAlert.show()
Now clickedButtonAtIndex function will be triggered.
You should use UIAlertViewController instead of UIAlertView because
UIAlertView is deprecated in iOS 9
Here is a code of UIAlertController in Swift and its pretty simple to use.The main thing is that it's Block based and No need to use any delegate
let alertController = UIAlertController(title: "Default AlertController", message: "A standard alert", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action:UIAlertAction!) in
println("you have pressed the Cancel button");
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction!) in
println("you have pressed OK button");
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
You need to set Delegate, so you can receive alerts callbacks:
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:self, cancelButtonTitle: "No"
I have a login screen with the username and password. I can currently load the username and password after the app is closed and opened back again, but I have to click the login button, instead I want it to perform the segue if the credentials are correct. I am using NSUserDefault to remember the credentials. Below is my code.
override func viewDidLoad() {
super.viewDidLoad()
if let usernameIsNotNill = defaults.objectForKey("username") as? String {
self.usernameField.text = defaults.objectForKey("username") as? String
}
if let passwordIsNotNill = defaults.objectForKey("password") as? String {
self.passwordField.text = defaults.objectForKey("password") as? String
}
// Do any additional setup after loading the view.
}
#IBAction func loginAction(sender: AnyObject) {
let username = self.usernameField.text
let password = self.passwordField.text
if(username!.utf16.count) < 4 || (password!.utf16.count < 5){
let alert = UIAlertView(title: "Invalid", message: "Username must be greater then 4 and Password must be greater then 5", delegate: self, cancelButtonTitle: "OK")
alert.show()
}else{
self.actInd.startAnimating()
PFUser.logInWithUsernameInBackground(username!, password: password!, block: { (user, error) -> Void in
self.actInd.stopAnimating()
if ((user) != nil) {
//var alert = UIAlertView(title: "Success", message: "Logged In", delegate: self, cancelButtonTitle: "OK")
//alert.show()
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(self.usernameField.text, forKey: "username")
defaults.setObject(self.passwordField.text, forKey: "password")
defaults.synchronize()
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier("login", sender: self)
})
}else {
let alert = UIAlertView(title: "Error", message: "\(error)", delegate: self, cancelButtonTitle: "OK")
alert.show()
}
})
}
}
You should not be storing a user's password in standard user defaults as plain text. If you're not writing an application that requires web access, then there's no point in making them log in really, and thus you wouldn't need to implement this. If a user needs an account for your app, I would suggest implementing one through a backend such as Parse which will happily handle the password more securely than you will.
override func viewDidAppear() {}
This function will excecute when the view is on the screen. So in this method go check for the credentials in NSUserDefaults. If theres some info in there excecute this
self.actInd.startAnimating()
PFUser.logInWithUsernameInBackground(username!, password: password!, block: { (user, error) -> Void in
self.actInd.stopAnimating()
if ((user) != nil) {
//var alert = UIAlertView(title: "Success", message: "Logged In", delegate: self, cancelButtonTitle: "OK")
//alert.show()
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier("login", sender: self)
})
}else {
let alert = UIAlertView(title: "Error", message: "\(error)", delegate: self, cancelButtonTitle: "OK")
alert.show()
}
})
But the username and password variables should be the ones from NSUserDefaults
Fisrt, check that user id and password are stored.
If they stored in device, check and push next viewcontroller.
For example,
let userDefaults = NSUserDefaults.standardUserDefaults();
if let userDefaults.objectForKey("username"){
if let userDefaults.objectForKey("password"){
// do login process
// if user id and password are valid then
let nextViewController = YourViewController();
self.presentViewController(nextViewController, animated: true, completion: nil);
}
}
// else alert some message and stay in loginViewController