I've created an app in which, when the user signs up, should create two values in the Firestore server in the user's document, that is level and subjects (meaning it's in /users/userid).
I've tried manually creating the 'users' collection, but nothing is being created when the user signs up.
The following is my code (SignUpViewController):
import Firebase
var reference: DocumentReference!
func firebaseAuth() {
let userDisplayName = textfieldDisplayName.text!
let userEmail = textfieldEmail.text!
let userPassword = textfieldPassword.text!
if userEmail == "" || userPassword == "" {
labelMessage.isHidden = false
labelMessage.textColor = UIColor.red
labelMessage.text = "Error: A compulsory field is left blank."
} else {
Auth.auth().createUser(withEmail: userEmail, password: userPassword) { (user, error) in
if user != nil && error == nil {
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = userDisplayName
changeRequest?.commitChanges(completion: { (error) in
if error == nil {
let userID = Auth.auth().currentUser?.uid
let dataToSave: [String: Any] = ["level":0, "subjects":[""]]
self.reference = Firestore.firestore().collection("users").document(userID ?? "")
self.reference.setData(dataToSave, completion: { (error) in
if error == nil {
self.performSegue(withIdentifier: "presentInitial", sender: self)
} else {
self.labelMessage.isHidden = false
self.labelMessage.textColor = UIColor.red
self.labelMessage.text = "Error: \(error?.localizedDescription ?? "")"
}
})
self.performSegue(withIdentifier: "presentInitial", sender: self)
} else {
self.labelMessage.isHidden = false
self.labelMessage.textColor = UIColor.red
self.labelMessage.text = "Error: \(error?.localizedDescription ?? "")"
}
})
} else {
self.labelMessage.isHidden = false
self.labelMessage.textColor = UIColor.red
self.labelMessage.text = "Error: \(error?.localizedDescription ?? "")"
}
}
}
}
The following code is from another View Controller which SignUpViewController redirects to (HomeViewController):
import Firebase
var reference: DocumentReference!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let userID = Auth.auth().currentUser?.uid
reference.getDocument { (docSnapshot, error) in // Fatal error occured here
let data = docSnapshot?.data()
let userLevel = data?["level"] as? String ?? ""
if userLevel == "" {
self.performSegue(withIdentifier: "performSetup", sender: self)
}
}
}
I expected that when redirected to the homepage (segued through presentInitial), the homepage will then read the value of 'level'. However, the app crashed with a fatal error: "Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value" where 'level' was meant to be read from the server.
I think the problem is not with Firestore. According to the error message, the code wraps an optional value but failed because it is nil, so the problem is probably about these three lines:
let userDisplayName = textfieldDisplayName.text!
let userEmail = textfieldEmail.text!
let userPassword = textfieldPassword.text!
Sometimes, when a UITextField has no text, its text is nil instead of "", which may cause the problem. You can replace the three lines with the following:
let userDisplayName = textfieldDisplayName.text ?? ""
let userEmail = textfieldEmail.text ?? ""
let userPassword = textfieldPassword.text ?? ""
In this way, these three variables will always be "" when there are no text, and your logic of checking blank fields will still work.
Edit: For future reference, the real problem is not in the question but in the comments of this answer.
Did you check your database rules ?
you need to setup the rules when to work and when not to so no one can access the database but your apps
for a test now use this code to verify it woks then you should change it
{
"rules": {
".read": true,
".write": true
}
}
Related
This question already has answers here:
UIDocumentPickerViewController is not working when testing with my iPad but fine with simulators
(2 answers)
Closed 12 months ago.
Update: This code works in the simulator, but not on my device. Obviously, I'm needing it to work on both.
I've followed the tutorials, yet I cannot seem to get this feature to work. When the user selects the barButtonItem, DocumentPicker opens allowing the user to select a .txt file. I then take the URL to the selected file and attempt to return a string from it; however, I'm getting the following error: "The file “Test.txt” couldn’t be opened because you don’t have permission to view it." What am I missing? Did I fail to ask for permission somewhere? I've tried cleaning the build folder - didn't work.
#IBAction func importFileBtnTapped(_ sender: Any) {
selectFiles()
}
func selectFiles() {
let types = UTType.types(tag: "txt",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let myURL = urls.first else {
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to retrieve document.")
return
}
let text = createStringFromSelectedFile(fileURL: myURL)
if text == "error" {
print("ERROR creating a string from the selected file.")
return
}
let separatedStrings = decipherString(text: text)
if separatedStrings.first == "error" {
print("ERROR deciphering the string in ClaimInfoViewController")
return
}
for string in separatedStrings {
print("\(string)")
}
print("import result: \(myURL)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func createStringFromSelectedFile(fileURL: URL) -> String {
var text = String()
do {
text = try String(contentsOf: fileURL)
}
catch {
print("ERROR in the createStringFromSelectedFile function in ClaimInfoViewController")
print("The error: \(error.localizedDescription)")
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to read the file. Please try again.")
return "error"
}
return text
}
func decipherString(text: String) -> [String]{
let newText = text
let startIndexes = ["<Claim#",
"<File#",
"<DateOfLoss:"
]
var claimNumber = String()
var fileNumber = String()
var dateOfLoss = String()
for indexValue in startIndexes {
guard let index = newText.firstIndex(of: ">") else { return ["error"] }
let newString = String(newText[..<index])
if indexValue == "<Claim#" {
claimNumber = newString
}
else if indexValue == "<File#" {
fileNumber = newString
}
else if indexValue == "<DateOfLoss:" {
dateOfLoss = newString
}
}
let finalText = [claimNumber, fileNumber, dateOfLoss]
return finalText
}
Thanks to matt, who commented above, I was able to find out that it's a security issue. Adding this simple code resolved the issue:
let shouldStopAccessing = pickedFolderURL.startAccessingSecurityScopedResource()
defer {
if shouldStopAccessing {
pickedFolderURL.stopAccessingSecurityScopedResource()
}
}
I added it right before this line of code that can be seen above:
let text = createStringFromSelectedFile(fileURL: myURL)
I got this code from here: StackOverflow Post
When I try to get user data from firebase I have an error. The error message is:
Value of type 'String' has no member 'documentID'
The line with the error is the line fetchUser(uid: uid.documentID) { (user) in:
let title = doc.document.data()["title"] as? String ?? "No Title"
let time = doc.document.data()["time"] as? Timestamp ?? Timestamp(date: Date())
let pic = doc.document.data()["url"] as? String ?? "No URL"
let uid = doc.document.data()["uid"] as? String ?? ""
// getting user Data...
fetchUser(uid: uid.documentID) { (user) in
And this is my FetchUser model:
// Global Refernce
let ref = Firestore.firestore()
func fetchUser(uid: String,completion: #escaping (UserModel) -> ()){
ref.collection("Users").document(uid).getDocument { (doc, err) in
guard let user = doc else{return}
let username = user.data()?["username"] as? String ?? "No Username"
let pic = user.data()?["imageurl"] as? String ?? "No image URL"
let bio = user.data()?["bio"] as? String ?? "No bio"
let uid = user.data()?["uid"] as? String ?? ""
DispatchQueue.main.async {
completion(UserModel(username: username, pic: pic, bio: bio, uid: uid))
}
}
}
Below is code to that checks the users uid. On the line that starts "ref.collection..." the error "Cannot find 'uid' in scope" is thrown.
func checkUser(){
let ref = Firestore.firestore()
if let currentUser = Auth.auth().currentUser {
let uid = currentUser.uid
} else {
print("No Authenticated User")
return
}
ref.collection("Users").whereField("uid", isEqualTo: uid).getDocuments { (snap, err) in
if err != nil{
// No Documents..
// No User Found...
self.registerUser.toggle()
self.isLoading = false
return
}
if snap!.documents.isEmpty{
self.registerUser.toggle()
self.isLoading = false
return
}
self.log_Status = true
}
}
Let me take the first part of your code to show where the issue is. Note how much easier it is to read when properly formatted
func checkUser() {
let ref = Firestore.firestore()
if let currentUser = Auth.auth().currentUser {
//NOTE! Note that this area is encapsulated with brackets { and }
//That means it's is own 'space' and anything defined in this area
//only exists in this area
let uid = currentUser.uid //<- uid only exists here
} else {
print("No Authenticated User")
return
}
//uid no longer exists and cannot be referenced
// e.g. it's not in 'scope' at this point
ref.collection("Users").whereField("uid", isEqualTo: uid)
However, if you look at where the let ref = Firestore line is located, it's at the top level within the checkUser function and will exist throughout the function.
There are many way to do this; here's one using a guard statement
func checkUser() {
let ref = Firestore.firestore()
guard let currentUser = Auth.auth().currentUser else {
print("no user!")
return
}
// currentUser flows through to here because it was created with
// a guard statement, so now we know it's populated and can
// get the uid property value from it
let uid = currentUser.uid
ref.collection("Users").whereField("uid", isEqualTo: uid)
guard is pretty neat in that it not only allows you to instantiate a var while a the same time as protecting your code from a nil situation, it also allows the var to flow through to the code following the guard.
I'm writting this answer as a community wiki, since the issue was resolved from the comments section, in order to provide a proper response to the issue reported.
The error came while trying to get the uid as fetchUser(uid: uid.documentID), instead the correct way is by doing fetchUser(uid: uid)
Then an error mentioning Document path cannot be empty appeared, which was mainly due to the fact that no entries with were loaded, the best way to avoid this is to load documents on the consulted path without nil values
I want to be able to check if an email address is already been used (so if somebody put test1#test.com but another user already registered with that email account).
I have a simple test if it has NOT been used an image view shows a green arrow, if it HAS been used then it is red x
when I create the user I use the following code
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
if error == nil {
self.ref.child("userEmails").child((user?.uid)!).setValue(email)
FIRAuth.auth()!.signIn(withEmail: email,
password: password)
} else {
//registration failure
}
what I am trying to do to check is
func checkIfEmailExists(textField: UITextField) {
let ref = FIRDatabase.database().reference()
let email = firstContainerTextField.text ?? ""
ref.child("userEmails").queryEqual(toValue: email)
.observe(.value, with: { snapshot in
if (self.firstContainerTextField.text?.isEmpty)! {
self.firstContainerImage.image = UIImage.init(named: "emptyBlue.png")
} else if !(self.firstContainerTextField.text?.isEmpty)! && !snapshot.exists() {
self.firstContainerImage.image = UIImage.init(named: "redEx.png")
} else if snapshot.exists() {
self.firstContainerImage.image = UIImage.init(named: "greenCheck.png")
}
});
}
So far it does not work as I can see in my database that test1#test.com exists.
Can somebody tell me what I missed?
EDIT
I have updated my code. I am using hasChildren and I searched for similar questions and they seem to point this direction, but I still cannot get the result I am looking for
func checkIfEmailExists(textField: UITextField) {
let ref = FIRDatabase.database().reference()
let email = firstContainerTextField.text ?? ""
ref.child("userEmails").queryEqual(toValue: email)
.observe(.value, with: { snapshot in
if !snapshot.hasChildren() {
self.firstContainerImage.image = UIImage.init(named: "redEx.png")
} else {
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
let tmp = child.value as! String
if tmp == email {
self.firstContainerImage.image = UIImage.init(named: "greenCheck.png")
}
}
}
});
}
Edit 2
I changed how I set my user up
self.ref.child("users").child((user?.uid)!).setValue(["Email": email])
so now my database looks like this
users
*****uid*****
Email: "test#test.com
As I commented earlier: you'll need to check whether the query has any results by calling snapshot.hasChildren().
func checkIfEmailExists(textField: UITextField) {
let ref = FIRDatabase.database().reference()
let email = firstContainerTextField.text ?? ""
ref.child("userEmails").queryEqual(toValue: email)
.observe(.value, with: { snapshot in
if (!snapshot.hasChildren()) {
// User doesn't exist yet...
}
});
}
The following is the structure of the Firebase function you might be looking for (Swift 4):
Auth.auth().fetchProviders(forEmail: emailAddress, completion: {
(providers, error) in
if let error = error {
print(error.localizedDescription)
} else if let providers = providers {
print(providers)
}
})
If the email address is already registered to a user, you will get a list of the providers that the email address is used for. Otherwise, the list of providers will be empty, and thus the email address is not registered.
I am trying to update a users email and full name. This is my code:
func saveTapped() {
var performSegue = false
if updateEmail.text == "" && updateFullName.text == "" {
self.cleanUrCodeRohan("Please fill in one or more of the missing text fields that you would like to update.")
}
if updateEmail.text != "" {
let user = FIRAuth.auth()?.currentUser
user?.updateEmail(updateEmail.text!) { error in
self.ref.child("users").child(self.currentUser).child("email").setValue(self.updateEmail.text!)
}
let emailUpdateRef = FIRDatabase.database().reference().child(currentUser).child("email")
print(emailUpdateRef)
emailUpdateRef.setValue(self.updateEmail.text)
performSegue = true
}
if updateFullName.text != "" {
let user = FIRAuth.auth()?.currentUser
if let user = user {
let changeRequest = user.profileChangeRequest()
changeRequest.displayName = self.updateFullName.text!
}
performSegue = true
}
if performSegue == true {
self.navigationController!.popViewControllerAnimated(true)
}
}
I am able to update the email under authorization but not under the database. Any help would be appreciated.
If JSON tree is something like this:-
appName{
users :{
userID :{
email : "..",
username : ".."
}
}
}
Use this Code to update your node's child value's:-
func saveTapped(){
if ((updateEmail.text != "" || updateFullName.text != "") && (updateEmail.text != nil || updateFullName.text != nil)){
let userRef = FIRDatabase.database().reference().child("users").child(FIRAuth.auth()!.currentUser!.uid)
if let new_Email = updateEmail.text as? String{
FIRAuth.auth()!.currentUser!.updateEmail(updateEmail.text!) { error in
if error == nil{
userRef.updateChildValues(["email" : new_Email ], withCompletionBlock: {(errEM, referenceEM) in
if errEM == nil{
print(referenceEM)
}else{
print(errEM?.localizedDescription)
}
})
}
}else{
self.cleanUrCodeRohan("Email couldn't be updated in auth")
}
}
if let new_Name = updateFullName.text as? String{
userRef.updateChildValues(["username" : new_Name ], withCompletionBlock: {(errNM, referenceNM) in
if errNM == nil{
print(referenceNM)
}else{
print(errNM?.localizedDescription)
}
})
}
}else{
self.cleanUrCodeRohan("Please fill in one or more of the missing text fields that you would like to update.")
}
}
I am making a simple app, and implementing the userInformation part. The user can edit his info, but I have trouble that if user doesn't put any info, it will crash when I try to retrieve data from an undefined column.
This is my code to retrieve the user data.now I can check a specific value, but still, I got a error with 'undefined' one.
var query = PFQuery(className: "Note")
query.getObjectInBackgroundWithId("kg8KhAWCms", block: {
(obj, error)in
if let obj = obj! as? PFObject {
let nickname = (obj.objectForKey("text")) as! String
if (nickname != nil) {
///// I have a error message that binary operator'!=' cannot be applied to operands of type 'String' and 'NiLiteralConvertible'
self.nickNameLabel.text = nickname
}else{
self.nickNameLabel.text = "you don't have a nick name"
}
} else {
print(error)
}
})
what is the 'NiLiteralConvertible' ?
and I've tried this as well,
var query = PFQuery(className: "Note")
query.getObjectInBackgroundWithId("kg8KhAWCms", block: {
(obj, error)in
if let obj = obj! as? PFObject {
let nickname = (obj.objectForKey("text")) as! String
if !(nickname.isEmpty) {
self.nickNameLabel.text = nickname
}else{
self.nickNameLabel.text = "you don't have a nick name"
}
} else {
print(error)
}
})
So I am asking how can I handle retrieving undefined value before crash? (please write full code for me)
///like this
if (undefined in parse == somekindOfType) {
print("yes")
}
You can't use nickname != nil because you have already said that it cannot be nil with let nickname = (obj.objectForKey("text")) as! String.
The as! String unwraps the obj.objectForKey("text") and at which it cannot be nil or you will get an error.
I suggest using the following:
if let nickname = obj.objectForKey("text") as? String {
self.nickNameLabel.text = nickname
}
else{
self.nickNameLabel.text = "you don't have a nick name"
}
Perhaps this would be beneficial to read: Swift Literal Convertibles