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
Related
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...!! :)
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.
so I am making an app that searches for friends locations saved on parse.com. Before I begin, here is the method in question.
- (void)saveFriendLocationInfo {
PFQuery *query = [PFQuery queryWithClassName:#"Locations"];
[query orderByDescending:#"createdAt"];
[query whereKey:#"userString" equalTo:currentFriend];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *parseCurrentFriend, NSError *error) {
if (!parseCurrentFriend) {
NSLog(#"The getFirstObject request failed.");
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Uh oh..."
message:#"Looks like that user doesnt exist. Might want to double check that."
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[self.navigationController popToRootViewControllerAnimated:YES];
[alertView show];
} else {
NSLog(#"ObjectID = %#", parseCurrentFriend.objectId);
parseCurrentFriend[#"title"] = friendTitle;
NSLog(#"Saved Title");
parseCurrentFriend[#"description"] = friendDescription;
NSLog(#"Saved Description");
parseCurrentFriend[#"latitude"] = friendLatitude;
NSLog(#"Saved Latitude");
parseCurrentFriend[#"longitude"] = friendLongitude;
NSLog(#"Saved Longitude");
}
}];
}
So this code all works fine at retrieving the required object. It reaches the NSLog line in the "else" statement where it is supposed to display the object ID. This displays the correct object id for the object that I am trying to receive. Now heres the weird part. The code seems to stop there. Nothing happens when trying to save the properties (title, description, lat, long) to the respective NSStrings; none of those other NSLog's below that are executed. But the weird thing is that there are no errors and the app keeps running as if that code below the first NSLog wasn't even there. Any insight into why this isn't working.
One problem you have is that you never save the object. After you modify all of the values, you need to save the PFObject.
[parseCurrentFriend saveInBackground];
Edit: Since you are trying to extract the values, then you'd need to do it like below.
friendTitle = parseCurrentFriend[#"title"];
friendDescription = parseCurrentFriend[#"description"];
friendLatitude = parseCurrentFriend[#"latitude"];
friendLongitude = parseCurrentFriend[#"longitude"];
I'm creating a UITableView with posts. If one of the tableview's rows is tapped, a detail page is shown where comments can be made to the post. I'm using the Parse.com framework to realize this.
I save the comments like this in the detailview:
PFObject *comment = [PFObject objectWithClassName:#"Comment"];
comment[#"content"] = _textViewComment.text;
comment[#"post"] = _object;
comment[#"user"] = currentUser;
[comment saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(!error){
UIAlertView* alertSave = [[UIAlertView alloc]initWithTitle:#"Saved" message:#"Your comment is saved" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertSave show];
[self setToDefault:_textViewComment];
}else{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Warning" message:#"Comment not saved" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}];
where _object is the current post.
What I want to do now is sort the first tableview by number of comments. But I'm only making a relation from the comment to the post and not the other way around.
Is there a way to query the posts and sort them by number of comments?
To do this, you should relate comments to posts via an array on Post.
Add a "comments" col of type array on Post. When you fetch the post, use includeKey: on the #"comments" key. Then change your update code to add a comment like this...
// in your code, I think "_object" is a post. calling it "post" here instead...
NSMutableArray *comments = [[post objectForKey:#"comments"] mutableCopy];
[comments addObject:comment];
[post setObject:comments forKey:#comments"];
// then save the post object. parse will save the related comment.
[post saveInBackgroundWithBlock ...
Double check everything in the data browser. Then, after the next fetch of posts, you can sort by comment count...
[query findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error) {
if (!error) {
NSComparator comparator = ^(Post *postA, Post *postB) {
NSNumber *countA = #([postA objectForKey:#"comments"].count);
NSNumber *countB = #([postB objectForKey:#"comments"].count);
return [countA compare:countB];
};
NSArray *sortedArray = [array sortedArrayUsingComparator:comparator];
// use sortedArray as your tableview datasource
}
}];
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: