I am using Parse to create user profiles. I have implemented user login and user sign up fields programmatically through the method below:
-(void) viewDidAppear:(BOOL)animated
{
PFLogInViewController *login = [[PFLogInViewController alloc] init];
login.fields = PFLogInFieldsUsernameAndPassword | PFLogInFieldsSignUpButton | PFLogInFieldsPasswordForgotten;
login.delegate = self;
login.signUpController.delegate = self;
login.signUpController.fields = PFSignUpFieldsUsernameAndPassword | PFSignUpFieldsAdditional | PFSignUpFieldsSignUpButton;
UIColor *color = [UIColor lightGrayColor];
login.signUpController.signUpView.additionalField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:#"Phone Number" attributes:#{NSForegroundColorAttributeName: color}];
if([PFUser currentUser])
{
[self dismissViewControllerAnimated:YES completion: nil];
if(!TimerOn)
{
CountNumber = 4;
Timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector: #selector(TimerCount) userInfo: nil repeats: YES];
TimerOn = true;
pictureButton.hidden = false;
TimerDisplay.hidden=false;
friendButton.hidden=false;
}
} else
{
[self presentViewController:login animated: YES completion: nil];
}
}
As you can see I have added an additional sign up field called phone number. I would like to make sure that each user has a unique phone number. While Parse checks that the user has a unique username and unique email address before adding the user to the PFUser class, it does not check for uniqueness in other fields. I have tried to get around this through the code below. However, this produces the error message of "* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'User cannot be deleted unless they have been authenticated via logIn or signUp'". This makes sense because in the code below I am searching for a user who has not yet been registered.
-(void) signUpViewController: (PFSignUpViewController *) signUpController didSignUpUser:(PFUser *)user
{
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query whereKey:#"additional" equalTo: signUpController.signUpView.additionalField.text];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error)
{
if (!error)
//If the phone number exists in the database then do the following
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Phone Number Error" message:#"Your digits are already in the system, Homie!" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
tempCurrentUser = user;
NSLog(#"%#", tempCurrentUser.objectId);
} else
{
//Start timer and do everything
if(!TimerOn)
{
[self dismissViewControllerAnimated:YES completion: nil];
CountNumber = 4;
Timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector: #selector(TimerCount) userInfo: nil repeats: YES];
TimerOn = true;
pictureButton.hidden = false;
TimerDisplay.hidden=false;
friendButton.hidden=false;
}
}
}];
}
One way I have tried to solve this is by using the following code. However when I do I receive the following warning: "Warning: A long-running Parse operation is being executed on the main thread. Break on warnParseOperationOnMainThread() to debug."
- (BOOL)signUpViewController:(PFSignUpViewController *)signUpController shouldBeginSignUp:(NSDictionary *)info
{
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query whereKey:#"additional" equalTo: signUpController.signUpView.additionalField.text];
PFObject *object = [query getFirstObject];
//If the phone number exists in the database then do the following
if (!object)
{
return true;
NSLog(#"The number is unique");
}
else
{
NSLog(#"the number is not unique");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Phone Number Error" message:#"Your digits are already in the system, Homie!" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
NSLog(#"%li", (unsigned long)requestArray.count);
return false;
}
}
You should do a query to check the uniqueness of the phoneNumber before you make the account. So instead of checking after the account is made, run a query like this before your proceed to signup:
PFQuery *query = [PFUser query];
[query whereKey:#"phoneNumber" equalTo:phoneNumber];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
PFUser *user = (PFUser *)object;
//This phone number already exists!!!
else {
//This phone number doesn't exist. Proceed to registration.
}
}
else {
//This phone number doesn't exist. Proceed to registration.
}
}];
While I do not see it in your code, it sounds like you are registering the user then checking the phone number field. If the number is found then you delete the user account.
There are a few options.
You can add cloud code for a before save that will check the phone number field.
You can check the phone number field BEFORE you save the account. (approach I would take).
If you choose #2 you will query parse for the phone number field. If you don't find the phone number in the database then you continue with the registration process. If you do, you alert the user and do not continue with the registration process.
You will want to override the shouldBeginSignUp https://parse.com/docs/ios/api/Protocols/PFSignUpViewControllerDelegate.html#//api/name/signUpViewController:shouldBeginSignUp:
Related
I am attempting to change the value of another PFUser field, from another unauthenticated PFUser in Parse, however I cant seem to do so. I am attempting to increase the number of 'hours' of one user from another user. Here is how I am attempting to do so:
PFUser *currentUser = [PFUser currentUser];
PFACL *ACL = [PFACL ACLWithUser:[PFUser currentUser]];
[ACL setPublicReadAccess:YES];
PFUser *selectedUser = [self.formValues objectForKey:#"user"];
NSLog(#"User ID: %#", selectedUser.objectId);
PFObject *volunteerSheet = [PFObject objectWithClassName:#"VolunteerSheet"];
volunteerSheet[#"userID"] = selectedUser.objectId;
volunteerSheet[#"fromID"] = currentUser.objectId;
volunteerSheet[#"volunteerTitle"] = [self.formValues objectForKey:#"title"];
volunteerSheet[#"location"] = [self.formValues objectForKey:#"location"];
volunteerSheet[#"volunteerHours"] = [self.formValues objectForKey:#"hours"];
volunteerSheet[#"volunteerDescription"] = [self.formValues objectForKey:#"description"];
volunteerSheet.ACL = ACL;
[volunteerSheet saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// The object has been saved.
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
// Retrieve the object by id
[query getObjectInBackgroundWithId:selectedUser.objectId
block:^(PFObject *user, NSError *error) {
[user incrementKey:#"volunteerHours" byAmount:(NSNumber*)[self.formValues objectForKey:#"hours"]];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(#"Succeeded");
}else{
NSLog(error.description);
}
}];
}];
NSLog(#"Saved");
[self dismissViewControllerAnimated:YES completion:^{
UIAlertView * alert =[[UIAlertView alloc ] initWithTitle:#"Success!"
message:#"Hours Sent succesfully."
delegate:self
cancelButtonTitle:nil
otherButtonTitles: nil];
[alert addButtonWithTitle:#"Okay"];
[alert show];
}];
} else {
// There was a problem, check error.description
NSLog(#"Error: %#",error.description);
}
}];
Users automatically have security put in place that disallow modifying another user (clps or alcs). Maybe consider moving to cloud code and calling use master key?
Users have ACLs set up to only allow themselves to change their values. To get around this, you either have to change the ACL every time you create a user to allow any user to have write permissions for them (VERY RISKY AND NOT SECURE AT ALL), or you need to call a cloud function that calls Parse.Cloud.useMasterKey() , which overrides all permissions, and will enable you to make the changes. Obviously, you should still be doing some sort of security check to make sure that these changes are allowed / proper changes before putting them through.
I'm trying to compare an NSString to an NSArray from parse with an if statement. Here's what I tried:
-(void)queryParseMethod {
//PFQuery *query = [PFQuery queryWithClassName:classNameString];
PFQuery *query = [PFQuery queryWithClassName:#"SaveClass2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
_barcodeArray = [[NSArray alloc]initWithArray:objects];
//[_tableView reloadData];
}else{
NSLOG(#"ERROR");
}
}];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self queryParseMethod];
NSIndexPath *indexPath;
int barcodInt = indexPath.row %1000000;
PFObject *barcodeObject = [_barcodeArray objectAtIndex:barcodInt];
NSArray *secondBarcodeArray;
secondBarcodeArray = [barcodeObject objectForKey:#"RNG1"];
if ([[secondBarcodeArray objectAtIndex:barcodInt] isEqualToString:_label.text]){
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"FOUND" message:#"THE BARCODE IS CORRECT!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
}else{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"NOT FOUND" message:#"THE BARCODE IS INCORRECT!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
};
}
But it always displays the "NOT FOUND" alert View and I'm sure that the classes are right and the columns are also right but it wont work. Any help will be appreciated.
As long as your _barcodeArray is assigned in background thread (where, I believe runs findObjectsInBackgroundWithBlock:, at point where You're trying to get object barcodeObject from _barcodeArray latter is empty (it can be or can be not). You have classic race condition. If you really need to run this find objects block in background, you should consider using some sync mechanism - like dispatch_semaphore. Or, as I presume, you don't need to call findObjectsInBackgroundWithBlock: and use its synchronous counterpart instead, if present.
The blocks are asynchronous so you can't know when block will run on main thread.
I read your code and I found that you are fetching object asynchronously. It means execution wont wait for that method findObjectsInBackgroundWithBlock: to complete.
So the solution is use the synchronous versions of this methods like...
- (NSArray *)findObjects;
- (NSArray *)findObjects:(NSError **)error;
the above method stops the execution further util the findObject: method not get executed.
Use this
-(void)queryParseMethod {
PFQuery *query = [PFQuery queryWithClassName:#"SaveClass2"];
NSError *error = nil;
[query findObjects:&error] {
if (!error) {
_barcodeArray = [[NSArray alloc]initWithArray:objects];
} else {
//error
}
}
I hope this will work for you...good luck...!! :)
so my application loads in a user's contacts and stores their phone numbers but I want to query these phone numbers against ones stored in Parse to determine whether any of their contacts are using the application.
So, I know how to work through every phone number as a single query by using something like the code below inside a for loop,
NSString *phoneNumber = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phoneNumbers, i));
//parse query for any matches to the phone number
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query whereKey:#"phoneNumber" equalTo:phonenumberfieldfriend.text];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d users.", objects.count);
// Do something with the found objects
if (objects.count == 0) {
//uialert letting the user know that no phone number matches the query
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No User"
message:#"No user matches this phone number"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
phonenumberfieldfriend.text = #"";
}
//if there is only one number matching the query
if (objects.count ==1) {
for (PFObject *object in objects) {
NSLog(#"%#", objects);
usernamefriend =[ object objectForKey:#"username"];
numberfriend = [object objectForKey:#"phoneNumber"];
firstnamefriend = [object objectForKey:#"firstName"];
lastnamefriend = [object objectForKey:#"lastName"];
emailfriend = [object objectForKey:#"email"];
add.hidden=true;
phonenumberfieldfriend.hidden=true;
confirmuser.hidden=false;
NSLog(#"one user entered %#",usernamefriend);
}
}
//if there is more than one phonenumber matching the query as
//the user to input the friends username
//instead
if (objects.count>1) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"More than one user!"
message:#"More than one user with this number please enter a username instead!"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
phonenumberfriend.text=#"Please enter a username";
add.hidden=true;
adduser.hidden=false;
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];}
but I would like to know whether it is possible to search all the contacts phone numbers with a single query?
You could do this numerous ways, provided I understand what your vision is:
One way is simply preform the query and simply do :
query getFirstObjectInBackground:
https://parse.com/docs/ios/api/Classes/PFQuery.html#//api/name/getFirstObjectInBackground
this narrows it down to only one phone number that matches your query parameters.
or getObjectInBackground:
So in other words just delete findObjects and replace with getFirstObjectInBackground and the first result that matches will be returned
Currently, I am attempting to optimize my getMutualFriends method. When I open my 'Friends' view controller, I execute the getMutualFriends method for every friend the user currently has... Which is NOT optimal...but was the easiest solution...
Heres what I did:
[CBUtility queryForFriends:[PFUser currentUser] block:^(NSArray *friends, NSError *error) {
[self.friendsActivityIndicator stopAnimating];
if (error) {
[[[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil cancelButtonTitle:#"Okay"
otherButtonTitles:nil]
show];
return;
}
if ([friends count] == 0 || !friends) {
[self.friendsTable addSubview:self.friendsEmptyView];
return;
}
self.friends = [NSMutableArray arrayWithArray:friends];
[self.friendsTable reloadData];
[self.friendsEmptyView removeFromSuperview];
int i = 0;
//
// THIS IS THE PART THAT SUCKS!!!
//
for (PFObject * friendObject in self.friends) {
[CBUtility queryForMutualFriends:[friendObject objectForKey:kCBFriendToUserKey] block:^(int mutualFriends, NSError *error) {
if (error) {
[[[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles:nil] show];
return;
}
CBFriendsCell *cell = (CBFriendsCell *)[self.friendsTable cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[cell setMutualFriends:mutualFriends];
}];
i++;
}
}];
And heres what +(void)queryForMutualFriends looks like:
+ (void)queryForMutualFriends:(PFUser *)user block:(void (^)(int number, NSError *error))completionBlock
{
PFQuery *usersFriends = [PFQuery queryWithClassName:kCBFriendClassKey];
[usersFriends whereKey:kCBFriendFromUserKey equalTo:user];
[usersFriends whereKey:kCBFriendStatusKey equalTo:kCBFriendStatusFriendKey];
PFQuery *currentUsersFriends = [PFQuery queryWithClassName:kCBFriendClassKey];
[currentUsersFriends whereKey:kCBFriendFromUserKey equalTo:[PFUser currentUser]];
[currentUsersFriends whereKey:kCBFriendStatusKey equalTo:kCBFriendStatusFriendKey];
[currentUsersFriends whereKey:kCBFriendToUserKey matchesKey:kCBFriendToUserKey inQuery:usersFriends];
[currentUsersFriends countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
if (!error) {
completionBlock(number, error);
return;
}
completionBlock(-1, error);
}];
}
So instead of running the loop and passing individual PFUser objects into the getMutualFriends method, I'd like to pass an array of friends into the method and return an array of dictionary objects whose keys are 'user' and 'count' with their respective values (e.g. #[#{#"user":somePFUser, #"count":5}, #{#"user":anotherPFUser, #"count":20}];
I mean, this works fine at the moment but takes up way too much API requests...
Anyone got ideas with how to setup the PFQuery?
EDIT:
Here was a link to a SQL query that solves the same problem
No apparently, you cannot... But you can limit the amount of times you query to the server by instead of querying for mutual friends when you retrieve the mutual friends like I did, you instead cache the results into memory...
I solved this issue by making the query in cellForIndexPath when setting a cells attributes. When the cell is loaded, I check cache first to see if the query has already been made, if it has then I get the cache data... If it hasn't then I make a query to the servers... Only issue I see is that it doesn't update... I figure I can clear cache every minute or so, so the user gets updated automatically instead of pressing a reload button.
I have implemented the following function (attached to the "register button") within my registration view controller as seen below. The function relies upon Parse and is seamless in general.
However I am encountering the following issues at present:
If a user inserts an invalid email address by mistake; the error string listed under "else" is activated (which is good) but the username and password entered above are registered regardless.
Users are able to leave the password field blank.
Any help whatsoever, especially pertaining to issue 1, would be immensely appreciated.
// Register user.
- (IBAction)registerUser:(id)sender
{
PFUser *user = [PFUser user];
user.username = self.mobileTextField.text;
user.password = self.passwordTextField.text;
user.email = self.emailTextField.text;
// Show loading HUD.
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
if (!error)
{
[self performSegueWithIdentifier:#"userRegistered" sender:self];
}
else
{
NSString *errorString = [[error userInfo] objectForKey:#"error"];
UIAlertView *errorAlertView = [[UIAlertView alloc] initWithTitle:#"Error" message:errorString delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[errorAlertView show];
// Dismiss loading HUD.
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
}
}];
// End editing.
[self.view endEditing: YES];
});
}
You have a number of problems here.
a, you absolutely do not need to and should not take a new thread. Parse does that for you. You must change it.
b, you may be looking for the magic formula in parse "if ( (!succeeded) || error)..."
c, you should really locally check that the email is valid before sending it. (ie, you can't enter "xyz#hotmail" or something not sensible as an email.)
ie, you need to write a routine like "checkThisEmailIsValid". if you need help with his yell out. note that it's not that easy conceptually. you understand that parse will try to verify the email right? ie it will send one of those emails "new user, click here to verify your email!" You're familiar with that?
d, a great secret is the 202 error code
Here's some example code from a production app, hope it helps!
-(void)_actuallyJoin
{
... do things like check the email is valid
... in this app the username is the lowercase email
PFUser *nuser = [PFUser user];
nuser.username = [self.email.text lowercaseString];
... in this app, the email is the email, password is the password
nuser.email = self.email.text;
nuser.password = self.password.text;
[APP huddie];
APP.hud.labelText = #"Registering ...";
APP.hud.detailsLabelText = #"1 of 3 ...";
... that is just MBProgressHUD.
[nuser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
if ( (!succeeded) || error)
{
[APP.hud hide:YES]; .. that's MBProgressHUD
.. usually, blank the form while app is connecting
self.email.text = #"";
self.password.text = #"";
self.confirmPassword.text = #"";
if ( error.code == 202 )
{
[PFAnalytics trackEvent:#"newAccount"
dimensions:#{ #"result":#"emailAlreadyUsed" }];
[self woe:#"That email address is already in use...."];
[PFUser logOut]; .. don't forget that
return;
}
[PFAnalytics trackEvent:#"newAccount"
dimensions:#{ #"result":#"connectionWoe" }];
[self woe:#"Couldn't connect. Please try later"];
return;
}
NSLog(#"step one rego success");
[self _actuallyJoinStepTwo];
... now continue to save other information
... for example user's address, age, avatar photo etc.
}];
}