I'm working on iOS Swift project with Parse. I need to allow users to update their email but it should be unique. Currently, my code looks like following:
var user = PFUser.currentUser()
var userName = user.username
var profQuery = PFQuery(className: "User")
profQuery.whereKey("email", equalTo: fnEditEmail)
profQuery.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects!.count < 1 {
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
object.setValue(self.fnEditEmail.text, forKey: "email")
object.setValue(self.fnEditAge.text, forKey: "age")
object.setValue(self.fnEditGender.text, forKey: "gender")
object.setValue(self.fnEditText.text, forKey: "fullname")
object.setValue(self.keyWord1.text, forKey: "key1")
object.setValue(self.keyWord2.text, forKey: "key2")
object.setValue(self.keyWord3.text, forKey: "key3")
object.setValue(self.keyWord4.text, forKey: "key4")
object.saveInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
println "Profile Updated."
} else {
println "Failed"
}
}
}
} else if error == nil && objects!.count >= 1 {
println "email already exist."
} else if error != nil {
println "couldn't update, please try again."
}
}
}
I don't think this is correct code and it's not working either. Could somebody please guide me how can I fit this and also, if I can prevent two PFQuery and findObjectsInBackgroundWithBlock, which I think what is required here; One to check if that email exists in current database and one to update the row.
Parse automatically detects this if you try to set the email field of a PFUser. For instance, when signing a user up to your application, Parse will return an error that the email is already being used and won't allow singup. In fact, it even presents the alert for you I'm pretty sure.
In any other part of your app if the user is trying to update their email, Parse will work the same way, albeit without the error presentation which you would have to take care of.
Because of this, you don't have to do any queries or anything. You would simply try to update the email field of a PFUser object, and save it, and Parse will return the error for you if such an email address already exists.
Bottom line is Parse will never allow non-unique email addresses for any PFUser, so you don't have to write code to worry about this. Just worry about email address validation if that's something you want, and then worry about presenting an alert if Parse returns an error.
var user = PFUser.currentUser()
user.setValue(newEmail, forKey: "Email")
user.saveInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
println "Profile Updated."
} else {
println "Failed"
//present alert to user to let them know that it failed
//ask them to try a new email address
}
}
Related
I am working on a function that handles user registration and in the process, check if the selected username entered by the user is taken or not to inform the user to select a different one. I have the below code to accomplish this scenario:
#IBAction func proceedPressed(sender: AnyObject) {
/**********************Perform Validation************************/
if(self.emailTxtField.text != "" && self.passwordTxtField.text != "")
{
print("Email and Password not empty")
self.usernameValidation({(result) -> Void in
if(result == false)
{
print("Result False")
self.usernameErrorLabel.text = "Username Taken"
}else{
print("Result True")
//Username is available...Proceed
self.usernameErrorLabel.text = ""
FIRAuth.auth()?.createUserWithEmail(self.emailTxtField.text!, password: self.passwordTxtField.text!) { (user, error) in
if(error == nil)
{
print("Creating User with Email")
/*Create the user object as submitted*/
self.dbReference.child("users").child(user!.uid).setValue(["username": self.emailTxtField.text!,"name":self.nameTxtField.text!, "email":self.emailTxtField.text!, "mobile":self.mobileTxtField.text!, "homeAddress":"N", "workAddress":"N", "otherAddress":"N", "profilePictureRef":"N","telephone":"0","friendsCount":0, "retailersCount":0])
}else{
print("Error occured: \(error?.description)")
}
}//end of createUserWithEmail
}
})
}else{
print("Error: Email or Password field is empty")
}
}
and to check the username:
func usernameValidation(completion: (result: Bool) -> Void)
{
print("Username is: \(self.usernameTxtField.text!)")
dbReference.child("usernamesTaken").queryOrderedByValue().queryEqualToValue(self.usernameTxtField.text!).observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot!) -> Void in
print(snapshot.childrenCount)
if(snapshot.childrenCount == 0)
{
print("result is true in username validation")
//Username Available
completion(result:true)
}else{
print("result is false in username validation")
//Username Taken
completion(result:false)
}
})
}
The problem with the above is that the full code doesn't seem to execute. When button pressed, I get the following messages in console:
- Email and Password not empty
- Username is: [value entered in usernameTxtField.text
and then nothing more. Although I wrote many print statements to try and see where this is stopping, but this is the furthest the code went in terms of printing the statements.
Is there something wrong here that I am missing out?
Thanks in advance.
I did some more testing and then discovered the issue through the xcode console. I copied the following from the firebase website to test fetching the data:
ref.child("users").child(userID!).observeSingleEventOfType(.Value, withBlock: { (snapshot) in
// Get user value
let username = snapshot.value!["username"] as! String
let user = User.init(username: username)
// ...
}) { (error) in
print(error.localizedDescription)
}
The above showed an error that is "Permission Denied". Following that I edited the Rules in the database section in the console and allowed .read and .write and that did it. I thought I would post the details just in case someone else gets stuck.
I've made a function that creates a "favorite" object back in parse with the tap of a favorite button on the UI :
//MARK: Create the favorite object
func createFavorite(){
let currentUser = PFUser.currentUser()
let currentBook = PFObject(withoutDataWithClassName: "Books", objectId: objectIdSelected)
let favorite = PFObject(className:"Favorites")
favorite["user"] = currentUser
favorite["books"] = currentBook
favorite.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// success
} else {
// There was a problem, check error.description
}
}
}
Now I am attempting to create a query that checks to see if a favorite object with those exact properties exists using the following logic, I've placed it in the viewDidLoad of a VC that shows a specific book:
//MARK: Check if there is a favorite object available
func isFavoritedByUser(){
let currentUser = PFUser.currentUser()
let currentBook = PFObject(withoutDataWithClassName: "Books", objectId: objectIdSelected)
let query = PFQuery(className:"Favorites")
query.whereKey("user", equalTo: currentUser!)
query.whereKey("books", equalTo: currentBook)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if objects != nil {
print("this book was favorited by this user")
} else if objects == nil {
print("this book WAS NOT favorited by this user")
} else if error == nil {
// succeed
} else if error != nil {
// There was a problem, check error.description
}
}
}
However, even if no favorite object with those properties exists back in my parse class, it still prints "this book was favorited by this user".. what am i missing here?
The list existing is not proof. That simply proves that some kinda of error weren't observed.
You should check that there was no error first, then you should check that the list of objects has one item in it.
If the list has more than one item then you're creating duplicates and you should fix that...
What you're getting returned is an array, as long as there isn't an error. So, rather than checking for nil, you should be checking the content of the returned array (objects) to see if there is anything in there.
In Parse's guide, you can see what they recommend for this function call:
https://parse.com/docs/ios/guide#queries
Additionally, Parse recommends considering getFirstObjectInBackground instead if this query only ever needs to return a single object, so that might be something to consider as an alternative.
So the problem was i was checking to see if object was nil rather then checking for the count, here is the correct code:
//MARK: Check if there is a favorite object available
func isFavoritedByUser(){
let currentUser = PFUser.currentUser()
let currentBook = PFObject(withoutDataWithClassName: "Books", objectId: objectIdSelected)
let query = PFQuery(className:"Favorites")
query.whereKey("user", equalTo: currentUser!)
query.whereKey("books", equalTo: currentBook)
query.findObjectsInBackgroundWithBlock { (object, error) -> Void in
if error == nil {
if object?.count > 0{
print("this book was favorited by this user")
} else {
print("this book WAS NOT favorited by this user")
}
} else {
}
}
}
For iOS, you can just check the dictionary on an object like so:
rideParse["driver"] == nil
If it exists, then you will get the pointer. If it doesn't/you did not query to include the pointer, then it will just return nil.
I am trying to create a function that takes a username as a parameter and checks to see if that username is taken (by comparing it to other PFUsers in the Parse database. This function is in my view controller class. (I know there are similar questions to this but they do not provide quality answers and are more general than this or are not in Swift).
func usernameIsTaken(username: String) -> Bool {
//bool to see if username is taken
var isTaken: Bool = false
//access PFUsers
var query : PFQuery = PFUser.query()!
query.whereKey("User", equalTo: username)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) in
if error == nil {
if (objects!.count > 0){
isTaken = true
println("username is taken")
} else {
println("Username is available. ")
}
} else {
println("error")
}
}
return isTaken
}
The problem is that the condition in the if statement is always false so "Username is available" always prints in the console even if the username is taken."Username is taken" is never printed even when the username is taken. What should I put in the nested if statement to check if the username matches another PFUser?
You are querying for User (class) key, but you need to query for a specific key, for example email.
// First get user's inputted email
let enteredEmailAddress = "sample#gmail.com"
// Then query and compare
var query = PFQuery(className: "_User")
query.whereKey("email", equalTo: enteredEmailAddress)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) in
if error == nil {
if (objects!.count > 0){
isTaken = true
println("username is taken")
} else {
println("Username is available. ")
}
} else {
println("error")
}
}
Just thought I would throw this out there, since this doesn't seem to be well known by people as I've answered a similar question before. Parse does this kind of checking for the user class automatically. If you're trying to create a new user with any of the default fields duplicated in Parse i.e username, email, etc, then Parse will not allow user signup. This is done automatically, with you having to do nothing, except for present the error so the user knows why they weren't able to sign up successfully. An example of signing a user up that checks for username email etc duplicates follows below:
user.signUpInBackgroundWithBlock {
(succeeded: Bool, signupError: NSError?)
-> Void in
if signupError == nil {
//present new controller
println("Signed up")
}
else {
if let errorString = signupError!.userInfo?["error"] as? NSString
{
error = errorString as String
}
else {
error = "We're sorry, an error ocured! Please try again."
}
self.displayAlert("Could not sign up", error: error)
}
}
}
Check the error code. Last time I did this, code 202 = Username Taken, code 203 = e-mail taken.
if signupError == nil {
print("User \(user.username!) signed up OK!")
} else if signupError?.code == 202 {
print("Username taken. Please select another")
} else if signupError?.code == 203 {
print("e-Mail taken. Please select another")
}
I use parse for my app. I want to let user able to type messages that they want to send via textField and save it to that user's messages column in parse with PFRelation via save button in view controller and the messages will be saved as an array and show it in tableView.
The problem is I don't know how to add text in textfield to an array and save it to parse.
Any help is appreciated and let me know if you need any additional information!
UPDATE:
These are screenshots of my parse's class "User"
This is my current user's friend list inside "Friends" column
I've not yet create Messages column because when run relationForKey code in Xcode it will automatically create for me
UPDATE 2:
This is my code:
#IBAction func addMessage(sender: AnyObject) {
var newMessage = addMessageText.text
let message = PFObject(className: "Messages")
var query = PFQuery(className: "Messages")
message["messageTextColumn"] = newMessage
message.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
println("added to Message Class")
query.whereKey("messageTextColumn", equalTo: newMessage)
query.getFirstObjectInBackgroundWithBlock{(object:PFObject!, error: NSError!) -> Void in
if error == nil {
let relation = PFUser.currentUser().relationForKey("Messages")
var addMessageWithObject = object
if addMessageWithObject != nil {
relation.addObject(addMessageWithObject)
println("Added with getFirstObject")
}
else{
println("Error Added with getFirstObject")
}
}
}
} else {
println("added to Message class Error")
}
}
}
I save new message to the array first and then I save it with saveInBackgroundWithBlock.. and inside I query that message to add it to relation.
The messages that I've added appear on Messages class table but not in that user's relation but it shows log
"added to Message Class" and "Added with getFirstObject"
Which means that my code execute exactly like it should be. Probably about the method?
UPDATE 3 this is the object println
<Messages: 0x7fd4484f75f0, objectId: LFXoSaHfQl, localId: (null)> {
ACL = "<PFACL: 0x7fd4484d2e70>";
messageTextColumn = 9;
}
UPDATE 4
this is my code
#IBAction func addMessage(sender: AnyObject) {
var newMessage = addMessageText.text
let message = PFObject(className: "Messages")
var user = PFUser.currentUser()
var query = PFQuery(className: "Messages")
message["messageTextColumn"] = newMessage
message.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
println("added to Message Class")
query.getFirstObjectInBackgroundWithBlock{(object:PFObject!, error: NSError!) -> Void in
if error == nil {
var addMessageWithObject = object
if addMessageWithObject != nil {
user.saveInBackground()
println("Added with getFirstObject")
}
else{
println("Error Added with getFirstObject")
}
}
}
}
}
}
user column is (undefined) as in screenshot here
and the error log can't add non pointer to relation is back
how do I fix this? Thanks!
Here's what you do:
Manually create your Message table on Parse
Add a messages column to your user table of type Relation with Target Class as your Message table.
In your code, in your buttons trigger:
// Get the message text from your textField
let messageText = textField.text
// Create your new Message object
let newMessage = PFObject(className: "Message")
// ... Add your data to your new message object
newMessage["messageTextColumn"] = messageText
newMessage.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// Add the new message to the user's relation
let relation = yourUser.relationForKey("messagesColumnName")
relation.addObject(newMessage)
// Save the user object
yourUser.saveInBackground()
} else {
// There was a problem, check error.description
}
}
Here's a link to Parse's Relation reference.
UPDATE:
Missing code to save your user object.
query.getFirstObjectInBackgroundWithBlock{(object:PFObject!, error: NSError!) -> Void in
if error == nil {
let relation = PFUser.currentUser().relationForKey("Messages")
var addMessageWithObject = object
if addMessageWithObject != nil {
relation.addObject(addMessageWithObject)
PFUser.currentUser().saveInBackground()
println("Added with getFirstObject")
}
else{
println("Error Added with getFirstObject")
}
}
}
UPDATE 2:
Messages without PFRelation:
Add a column (let's say user) of type Pointer with Target Class as _User to the Messages table to identify each message by their user.
Saving new messages: Save the new message object like above (just without adding the relation and it'e related code):
#IBAction func addMessage(sender: AnyObject) {
var newMessage = addMessageText.text
let message = PFObject(className: "Messages")
message["messageTextColumn"] = newMessage
message["user"] = PFUser.currentUser()
message.saveInBackgroundWithBlock {(success: Bool, error: NSError?) -> Void in
if (success) {
println("added to Message Class")
} else {
// Error saving message
}
}
}
Querying the messages for a user: You can query using the current user as a constraint so no matter which device a particular switches to, he/she will get only his messages.
var query = PFQuery(className:"Messages")
query.whereKey("user", equalTo:PFUser.currentUser())
query.findObjectsInBackgroundWithBlock {
...
}
I have already submitted a question on how to retrieve an objectId (swift - retrieve objectId field causes fatal error) but that seems to only apply to objects during a save because I have spent a long time (days) trying to retrieve the objectId from a saved row in a Parse cloud DB with absolutely no luck. I've mimicked the code in the Parse object retrieval documentation (https://parse.com/docs/ios_guide#objects-retrieving/iOS), using the special values provided for objectId's:
let objectId = gameScore.objectId
I was getting a crash every time but now the app just stops after printing the successful login message. Here is the code and sequence of events.
I signup a new user (let's call him vin).
The signup code:
#IBAction func signUp(sender: AnyObject) {
if countElements(self.userNameTextField.text) > 0 && countElements(self.passWordTextField.text) > 0 {
var megabyker = PFUser()
user.username = self.userNameTextField.text
user.password = self.passWordTextField.text
user.signUpInBackgroundWithBlock{
(succeeded:Bool!, error:NSError!)->Void in
if error == nil {
println("Sign Up successfull")
//associate current user for parse remote push notifications
var installation:PFInstallation = PFInstallation.currentInstallation()
installation.addUniqueObject("goride", forKey: "channels")
installation["user"] = PFUser.currentUser()
installation.saveInBackground()
//default row for allData table
//save default settings row for GUEST user
var alldata:PFObject = PFObject(className: "allData")
alldata["divvy_routes"] = "ON"
alldata["sortBy"] = "distance"
alldata["totalRides"] = 0
alldata["username"] = user.username
//self.useGuestUser = "USER"
alldata["user"] = PFUser.currentUser()
alldata.saveInBackgroundWithBlock{(success:Bool!, error:NSError!) ->Void in
if success != nil {
NSLog("%#","OK-alldata REAL data saved")
self.allDataSignUpObjectId = alldata.objectId
println("printing out objectId var in ALLDATA REAL section: ")
println(self.allDataSignUpObjectId)
self.performSegueWithIdentifier("go-to-main-menu", sender: self)
}
else
{
NSLog("%#",error)
}
}
} else {
let alert = UIAlertView()
alert.title = "Invalid Signup"
alert.message = "Sorry, a username (unique) and a password are required to create a new account."
alert.addButtonWithTitle("OK")
alert.show()
}
}
}
else
{
let alert = UIAlertView()
alert.title = "Invalid Signup"
alert.message = "Sorry, a username (unique) and a password are required to create a new account."
alert.addButtonWithTitle("OK")
alert.show()
}
}
The signup code works fine. It creates a new user and row in the User table and new default row in the allData table. The allData table has a user pointer column to associate it with the User table. I grab the objectId and assign it to an instance variable because I want to pass it to another viewcontroller to use:
self.allDataSignUpObjectId = alldata.objectId
println(self.allDataSignUpObjectId)
And here is a screenshot of the ParseDB where you can see the row has been saved in the user table for the new user. His objectId is 9vRF8BvdlZ and the row in the allData table has an objectId of DEaj0iWZ3X. This is the objectId I am trying to get (DEaj0iWZ3X).
allData table:
Now here is the allData where the new user's default setting row is inserted and it's automatically linked to the Vin's user table:
I then go to the Settings view controller and Save as the newly created user:
#IBAction func updateSettings(sender: AnyObject) {
var alldata:PFObject = PFObject(className:"allData")
var user = PFUser.currentUser()
var query = PFQuery(className:"allData")
query.whereKey("user", equalTo: user)
var id = allDataPass
println(id)
query.getObjectInBackgroundWithId(id) {
(alldata: PFObject!, error: NSError!) -> Void in
if error != nil {
NSLog("%#", error)
} else {
alldata["routes"] = self.RoutesSetting
alldata.saveInBackground()
}
}
}
It saves with no problems and the updated time in the row updates.
But when I logback in as Vin (after resetting the contents and settings on the simulator to get a fresh start), I get to LOGIN but it just stops (not crash, just stops and won't get past the login screen).
Here's the console printout:
2015-02-26 22:19:01.732 MegaByke[51906:6996084] Location Services Not Determined, must ask user
2015-02-26 22:19:01.734 MegaByke[51906:6996084] location services enabled: true
Location services success - <MegaByke.AppDelegate: 0x7ff4b145bd80>
2015-02-26 22:19:15.700 MegaByke[51906:6996084] Reachability Flag Status: -R -----l- networkStatusForFlags
<PFUser: 0x7ff4b167f830, objectId: 9vRF8BvdlZ, localId: (null)> {
username = vin;
}
Login successfull
objectId preFETCH
nil
Here is my login code:
#IBAction func login(sender: AnyObject) {
switch reachability!.currentReachabilityStatus().value{
case NotReachable.value:
var alert = UIAlertView(title: "No Internet Connection", message: "In order to login, you must have internet connection. You can still login as a Guest", delegate: nil, cancelButtonTitle: "Dismiss")
alert.show()
default:
if countElements(self.userNameTextField.text) > 0 && countElements(self.passWordTextField.text) > 0 {
PFUser.logInWithUsernameInBackground(userNameTextField.text, password: passWordTextField.text){
(user:PFUser!, error:NSError!)->Void in
if user != nil
{
println(user)
println("Login successfull")
//associate current user for parse remote push notifications
var installation:PFInstallation = PFInstallation.currentInstallation()
installation.addUniqueObject("goride", forKey: "channels")
installation["user"] = PFUser.currentUser()
installation.saveInBackground()
var user = PFUser.currentUser()
var query = PFQuery(className:"allData")
query.whereKey("user", equalTo: user)
// Retrieve the most recent one
query.orderByDescending("createdAt")
query.limit = 1;
//assign objectId
var alldata = PFObject(className:"allData")
println("objectId preFETCH")
println(alldata.objectId)
//refresh object
alldata.fetch()
let id = alldata.objectId
println("objectId postFETCH")
println(id)
query.getObjectInBackgroundWithId(id) {
(alldata: PFObject!, error: NSError!) -> Void in
if error == nil {
//Load parse data from cloud
NSLog("%#", alldata)
self.performSegueWithIdentifier("go-to-main-menu", sender: self)
} else {
NSLog("%#", error)
}
}
}
else { // Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
}
else{
println(user)
let alert = UIAlertView()
alert.title = "Invalid Login"
alert.message = "Sorry, that username and/or password is incorrect. Please enter a valid combination."
alert.addButtonWithTitle("OK")
alert.show()
println("Login failed")
}
}
}
The println that's supposed to occur after the fetch never gets to the console so I'm assuming I have not successfully retrieved the objectId. I have also checked Parse's user forum and no help there.