PFQuery to find PFUser whose key matches NSString - ios

I first of all apologize for the most random rambling title ever. Here is my situation. I populate a UITableView by getting a list of all the Facebook friends a user has that also use the app. I get this by:
[FBRequestConnection startForMyFriendsWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
// result will contain an array with your user's friends in the "data" key
self.friendObjects = [result objectForKey:#"data"];
self.jobsTemp = [[NSMutableArray alloc] initWithCapacity:self.friendObjects.count];
NSLog(#"%#", self.friendObjects);
for(NSDictionary *jobsInfo in self.friendObjects) {
FriendArray *jobby = [[FriendArray alloc] init];
jobby.name = [jobsInfo valueForKey:#"name"];
NSLog(#"Name%#", jobby.name);
// jobby.name = jobsInfo[#"additional"];
jobby.facebookid = jobsInfo[#"id"];
[self.jobsTemp addObject:jobby];
}
self.jobsArray = self.jobsTemp;
NSLog(#"ARRAY%#", self.jobsArray);//set #property (nonatomic, copy) NSArray *jobsArray; in the .h
[self.tableView reloadData];
}
}];
Each entry in _User has a column called fbId. What I would like to do is search all PFUsers when a row is clicked and find the PFUser that has a fbId entry that matches the id for that particular row. What I have tried so far is:
FriendArray *job = self.jobsArray[indexPath.row];
PFObject *object = [PFObject objectWithClassName:#"_User"];
PFQuery *query = [PFUser query];
[query whereKey:job.facebookid equalTo:object[#"fbId"]];
NSLog(#"Query%#", [query findObjects]);
However, I cannot figure out what I am doing wrong. Any suggestions?

You will need to create two PFQuery objects. One to represent the first user, the second to find the friend.
The Parse API query method you are looking for is:
- (instancetype)whereKey:(NSString *)key matchesKey:(NSString *)otherKey inQuery:(PFQuery *)query
You will do something like:
[friendQuery whereKey:#"id" matchesKey:#"fbId" inQuery:userQuery]

Related

Best way to fetch child and parent objects with Parse Server in iOS

I'm trying out the "Sample Blog App" on Parse Server for iOS and cannot figure out what is the smartes way to fetch all child objects of another class (together with the parent objects).
The "Sample Blog App" (which creates automatically when you create a new account) contains the classes Comment and Post. The Comment class contains a relation to the Post class as shown below (from the dashboard), but there is no relation in the opposite direction.
Now, I want to fetch all posts and all the comments related to each post. The code below does that, but I'm assuming there must be a smarter way...? If you know how, please share. Thanks in advance!
- (void)fetchPosts {
NSString *commentsKey = #"comments";
NSString *postKey = #"post";
PFQuery *query = [PFQuery queryWithClassName:#"Comment"];
[query includeKey:postKey];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
if (error == nil) {
NSMutableArray *array = [[NSMutableArray alloc]init];
for (PFObject *comment in objects) {
PFObject *post = [comment objectForKey:postKey];
NSDictionary *existingPostDict = [[array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K = %#", #"post.objectId", post.objectId]] firstObject];
if (existingPostDict) {
// update comments
NSArray *comments = [[existingPostDict objectForKey:commentsKey] arrayByAddingObject:comment];
// create new dictionary and replace the old one
NSDictionary *newPostDict = [[NSDictionary alloc]initWithObjectsAndKeys:[existingPostDict objectForKey:postKey], postKey, comments, commentsKey, nil];
[array replaceObjectAtIndex:[array indexOfObject:existingPostDict] withObject:newPostDict];
}
else {
// first post, create a new dict
NSDictionary *newPostDict = [[NSDictionary alloc]initWithObjectsAndKeys:post, postKey, #[comment], commentsKey, nil];
[array addObject:newPostDict];
}
}
self.posts = array; // assuming: #property (nonatomic, strong) NSArray *posts;
}
else {
NSLog(#"Error fetching posts: %#", error.localizedDescription);
}
}];
}
Instead of using include on your query you should use whereKey:equals: and pass the post object as the second argument. This will filter and return only the comment objects that contain that have that post as their value for post
One problem I see with your query is that there is a possibility this will not fetch every post in the database. If a post has 0 comments, none of the Comment objects will have a reference to it and thus you will not receive it.
Therefore you should actually do a query on "Post" and in its completion do a query on "Comment". This way you will not miss any posts with 0 comments. When you do this, you will not need to include the "post" key in the Comment query. This has multiple benefits.
First, each include is also another query for that object. So each new Comment object will create another query in the backend. You will get rid of this automatically.
Second, for a "Post" with multiple comments, you will be querying for the same post multiple times and that same post will be returned multiple times which consumes unnecessary bandwidth.
After getting Posts and Comments separately just combine them.
Apart from that I would do the combining like so which I find more readable but that is just personal preference.
- (void)fetchPosts {
NSString *commentsKey = #"comments";
NSString *postKey = #"post";
PFQuery *query = [PFQuery queryWithClassName:#"Comment"];
[query includeKey:postKey];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
if (error == nil) {
NSMutableArray *array = [[NSMutableArray alloc]init];
NSMutableDictionary *d = [NSMutableDictionary dictionary];
for (PFObject *comment in objects) {
PFObject *post = [comment objectForKey:postKey];
if (d[post.objectId]) {
[d[post.objectId][commentsKey] addObject:comment];
}
else{
d[post.objectId] = [NSMutableDictionary dictionary];
d[post.objectId][postKey]=post;
d[post.objectId][commentsKey] = [NSMutableArray arrayWithObject:comment];
}
}
for (NSString *key in [d allKeys]) {
[array addObject:d[key]];
}
self.posts = array; // assuming: #property (nonatomic, strong) NSArray *posts;
}
else {
NSLog(#"Error fetching posts: %#", error.localizedDescription);
}
}];
}
This is how I did it, using findObjectsInBackground together with continueWithSuccessBlock: methods (for better error handling one can choose continueWithBlock: instead):
- (void)fetchPosts {
/**
create "post" and "comment" queries and use a BFTask-method from
Bolts.framework to chain downloading tasks together (bundled with Parse SDK)
*/
NSMutableArray *posts = [NSMutableArray new];
PFQuery *postQuery = [PFQuery queryWithClassName:#"Post"];
[[[postQuery findObjectsInBackground] continueWithSuccessBlock:^id(BFTask * task) {
[posts addObjectsFromArray:task.result];
PFQuery *commentsQuery = [PFQuery queryWithClassName:#"Comment"];
return [commentsQuery findObjectsInBackground];
}] continueWithSuccessBlock:^id(BFTask * task) {
/**
loop through posts and filter out comments with the same objectId in post,
then create a dictionary with post and related comments. done! :)
*/
NSMutableArray *postsAndComments = [NSMutableArray new];
for (PFObject *post in posts) {
NSArray *comments = [task.result filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"post.objectId", post.objectId]];
[postsAndComments addObject:#{#"post":post, #"comments":comments}];
}
/**
note: BFTask-blocks not running in main thread!
*/
dispatch_async(dispatch_get_main_queue(), ^{
self.posts = postsAndComments; // assuming: #property (nonatomic, strong) NSArray *posts;
});
return nil;
}];
}

Why is my NSMutable not being populated correctly when inserting objects from another NSArray?

I have a PFQuery that gets the current participants of a particular event:
PFQuery *getcurrentparticipants = [PFQuery queryWithClassName:#"Event"];
[getcurrentparticipants selectKeys:#[#"Participants"]];
[getcurrentparticipants whereKey:#"objectId" equalTo:ObjectID];
[getcurrentparticipants findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSMutableArray *newParticipantsArray = [[NSMutableArray alloc]init];
if([objects[0] valueForKey:#"Participants"] == nil){ // If object retrieved in objects is null. If there are 0 participants
[newParticipantsArray addObject:PFUser.currentUser.username];
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query getObjectInBackgroundWithId:self.ObjectID
block:^(PFObject *Event, NSError *error) {
Event[#"Participants"] = newParticipantsArray;
[Event incrementKey:#"Vacants" byAmount:[NSNumber numberWithInt:-1]];
[Event saveInBackground];
}];
}else{ // STEP 5
for(int i=0;i<objects.count;i++) {
[newParticipantsArray addObject:[[objects objectAtIndex:i] valueForKey:#"Participants"]];
}
[newParticipantsArray addObject:PFUser.currentUser.username];
NSLog(#"Part to upload %#", newParticipantsArray);
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query getObjectInBackgroundWithId:self.ObjectID
block:^(PFObject *Event, NSError *error) {
Event[#"Participants"] = newParticipantsArray;
[Event incrementKey:#"Vacants" byAmount:[NSNumber numberWithInt:-1]];
[Event saveInBackground];
}];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
This is how the method works:
Create a PFQuery object
Query the Participants Class for an specific ObjectId
If no error, then we create a NSMutable array
If no participants are in Parse then we insert the current user as participant.
Else, insert all participants in the mutable array and add currentuser at the end of the array.
Then upload it again to Parse
My problem is in step 5:
When I perform the tasks in the else, the column in Parse looks like this :
[["Participant 1"],"Participant 2"]
But I would like to have it like this:
["Participant 1","Participant 2"]
What I have tried:
I tried things like putting the arrays like this. [newParticipantsArray addObject:[[objects objectAtIndex:i] valueForKey:#"Participants"]]; and similar combinations, of course without luck.
It’s hard to say for sure since I can’t see the structure of your data but are you sure the value held in
[[objects objectAtIndex:i] valueForKey: #“Participants”]
Is a single user and not itself an array of users? The plural key “participants” seems to suggest it’s an array of users which would also explain the result you’re getting.
If in fact the value returned for the "Participants" key is an array, you can add the objects in it to your mutable array by doing the following:
NSArray* participants = [[objects objectAtIndex:i] valueForKey:#"Participants"]
[newParticipantsArray addObjectsInArray:participants];
This uses the addObjectsInArray: method of NSMutableArray to add the objects from the old array into the new one.

Parse relation string

I'm developing an iOS app to parse data
I have a class called #"name" this class contains a string column [name]
and a class called #"email" this class contains a string column[email]
the class are already related and no have problem with that
In a different ViewController have two TextField and one button, on a TextField the user enters a valid name string (name)
The button display should show in the second TextField the email associated with the name entered. I want to associate this data.
//relation class
PFObject *data = [PFObject objectWithClassName:#"nameContactMe"];
data[#"name"] = _name.text;
PFObject *DataRequest = [PFObjec objectWithClassName:#"mailContactMe"];
DataRequest[#"email"] = _email.text;
DataRequest[#"relation"] = data;
[DataRequest saveInBackground];
here the disaster
//get relation
- (IBAction)GetDate:(id)sender {
PFRelation *relation = [user relationForKey:#"relation"];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// The post has been added to the user's likes relation.
} else {
// There was a problem, check error.description
}
}];
PFObject *data = _Visitor.text = #"relation";
[data fetchIfNeededInBackgroundWithBlock:^(PFObject *post, NSError *error) {
emailField.text = data[#"email"];
// do something with your title variable
}];
}
I think you should use PFQuery for fetching the desired email :
PFQuery *query = [PFUser query];
[query whereKey:#"name" equalTo:name];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
NSString *email = [object valueForKey:#"email"];
}
If you want to fetch the email of current user(Logged in user) then you can use this :
NSString *email =[[PFUser CurrentUser] [#"email"];

Parse.com returns empty objects

I have an NSArray called "malls" that contains a large number of NSDictionaries (each a specific mall) that I uploaded to Parse.com. I want my users to be able to access this information to create map annotations.
I've tried to do this in 2 different ways:
I tried uploading the entire array as a property of a single object:
this is the upload:
in the dataBank.h file:
#property (strong, nonatomic) NSMutableArray* malls;
in the .m file
PFObject *obj = [PFObject objectWithClassName:#"malls"];
obj[#"mallsData"] = self.malls;
[obj saveInBackground];
I try to get the data from parse:
-(NSMutableArray *)createAnnotationsFromParse
{
__block NSMutableArray* data = [[NSMutableArray alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls"];
[query getObjectInBackgroundWithId:#"Eaib9yfTRe" block:^(PFObject *object, NSError *error) {
data = [object objectForKey:#"mallsData"];
annots = [self createAnnotations:data];
}];
return annots;
}
The problem is getObjectInBackground is asynchronous and always returns before getting the data from the server. I tried moving the "return annots" inside the code block but that gives the following error: "incompatible block pointer types".
I uploaded 5 "mall" objects to class "malls2". Each object has 2 properties- name and address:
for(int i = 0; i < 5; i++)
{
PFObject *mallsObj = [PFObject objectWithClassName:#"malls2"];
mallsObj[name] = [[self.malls objectAtIndex:i]objectForKey:name];
mallsObj[address] = [[self.malls objectAtIndex:i]objectForKey:address];
[mallsObj saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded)
NSLog(#"yay");
else
NSLog(#"%#", error.description);
}];
}
then I try to get it back:
-(NSMutableArray *)createAnnotationsFromParse
{
__block Annotation* anno = [[Annotation alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(error)
NSLog(#"%#", error.description);
else
{
for(int i = 0; i < [objects count]; i++)
{
//createAnnotationWithTitle is a func in a different class that creates the annotation
anno = [anno createAnnotationWithTitle:[[objects objectAtIndex:i] objectForKey:name] andAddress:[[objects objectAtIndex:i]objectForKey:address]];
}
[annots addObject:anno];
}
}];
return annots;
}
I get 5 objects but they're all empty.
It's a basic misunderstanding about asynchronous methods with block parameters. The trick is to get out of the habit of thinking that code that appears later in a source file runs later. The assumption works in this function:
- (void)regularFunction {
// these NSLogs run top to bottom
NSLog(#"first");
NSLog(#"second");
NSLog(#"third");
}
This will generate logs: first, second, third. Top to bottom, but not in this one:
- (void)functionThatMakesAsynchCall {
// these NSLogs do not run top to bottom
NSLog(#"first");
[someObject doSomeAsynchThing:^{
NSLog(#"second");
}];
NSLog(#"third");
}
That function will generate logs - first, third, second. The "second" NSLog will run well after the "third" one.
So what should you do? Don't try to update the UI with results of a parse call until after it completes, like this:
// declared void because we can't return anything useful
- (void)doSomeParseThing {
// if you change the UI here, change it to say: "we're busy calling parse"
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
// change the UI here, say by setting the datasource to a UITableView
// equal to the objects block parameter
}
}];
// don't bother changing the UI here
// don't bother returning anything here
// we just started the request
}
But what if doSomeParseThing is really a model function, whose only job is to fetch from parse, not to know anything about UI? That's a very reasonable idea. To solve it, you need to build your model method the way parse built their's, with block parameter:
// in MyModel.m
// declared void because we can't return anything useful
+ (void)doSomeParseThing:(void (^)(NSArray *, NSError *))block {
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
block(objects, error);
}];
}
Now your view controller can call, leave the query work to your model and the UI work to the vc:
// change UI to 'busy' here
[MyModel doSomeParseThing:^(NSArray *objects, NSError *error) {
// update UI with objects or error here
}];
Figured it out. It looked like I was getting "empty objects" (can be seen here postimg.org/image/ot7ehn29b ) but once I tried to access data from the objects I saw there was no problem. Basiclly I was tricked by the PFObjects in the array showing "0 objects" and assumed it meant they came back from Parse.com empty. Here's how I checked, just for reference:
PFQuery *query = [PFQuery queryWithClassName:#"malls2"];
NSArray *array = [query findObjects];
NSLog(#"%#", [[array objectAtIndex:0] objectForKey:#"name"]; // I have a string property called "name" in my Parse object.

How to run a loop of parse queries?

I am making an app that registers users and allows them to add friends etc. So I have a LoginViewController where I retrieve the array of user's friends' objectIds when the login is successful. This function is called.
- (void) getFriendList
{
NSString * objectID = [PFUser currentUser].objectId;
NSLog(#"%#", objectID);
PFQuery *query = [PFUser query];
[query getObjectInBackgroundWithId:objectID block:^(PFObject *username, NSError *error) {
sharedClass.sharedInstance->retrievedFriends = username[#"friendsIds"];
friendListLoaded = YES;
[self getFriendsUsernames];
}];
Here i get an array object that contains object ids of all the friends this user has. As you can see when getFriendList is completed, it calls another function called GetFriendsUsernames. This function is supposed to retrieve profile pictures and usernames of those friends so I can populate the Friend List view later.
-(void) getFriendsUsernames
{
NSMutableArray * objectIDs = [[NSMutableArray alloc] initWithArray: sharedClass.sharedInstance->retrievedFriends];
PFQuery *query = [PFUser query];
int friendsCount = [objectIDs count];
for(int i = 0; i<=friendsCount;i++)
{
[query getObjectInBackgroundWithId:objectIDs[i] block:^(PFObject *username, NSError *error) {
[sharedClass.sharedInstance->friendsUsernames addObject:username[#"username"]];
[sharedClass.sharedInstance->friendsProfilePictures addObject:username[#"ProfilePicture"]];
}];
NSLog(#"%#", sharedClass.sharedInstance->friendsUsernames );
}
}
But this seems to be unsuccessful because nothing is logged on the console where it should log username of retrieved friend whenever one query gets finished. Am I doing this the right way? My array count is right so loop runs to the number of friends a user has. It also prints the username of first object in objectIds array if i put the logging command in the loop.
Your NSLog runs immediately after your query objects are queued for execution. Therefore, even if the code is running correctly (and I suspect it might not be), you'll never get the correct results you're after logged to the console.
If you have your Parse classes designed in a certain way, you could collapse this into 1 query by using the include method on PFQuery. This assumes that you've created an array of Pointers on your User object, named "friends". If you actually store the objectId (i.e., the string value) of each friend, this code won't work.
I'm not using Swift yet, so here's how I'd write this query in Objective-C:
- (void)getFriendList {
PFUser *currentUser = [PFUser currentUser];
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:currentUser.objectId];
[query includeKey:#"friends.username"];
[query includeKey:#"friends.ProfilePicture"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *user, NSError *error) {
if (error != nil) {
// Process the error
} else }
// You've got your user and the data you wanted
}
}];
}
Found it. Not the prettiest way out there but well it does what i want. gets me arrays of usernames and profile pictures of every objectID contained in array of friends.
NSMutableArray * objectIDs = [[NSMutableArray alloc] initWithArray: sharedClass.sharedInstance->retrievedFriends];
PFQuery *query = [PFUser query];
PFFile * imageFile;
//UIImage *profilePictureData;
int friendsCount = [objectIDs count];
for(int i = 0; i<friendsCount;i++)
{
PFObject * username = [query getObjectWithId:objectIDs[i]];
[sharedClass.sharedInstance->friendsUsernames addObject:username[#"username"]];
[sharedClass.sharedInstance->friendsEmailAdresses addObject:username[#"email"]];
//NSLog(#"%#",username[#"ProfilePicture"]);
imageFile = [username objectForKey:#"ProfilePicture"];
NSData *imageData = [imageFile getData];
UIImage *imageFromData = [UIImage imageWithData:imageData];
[sharedClass.sharedInstance->friendsProfilePictures addObject:imageFromData];
NSLog(#"%#", sharedClass.sharedInstance->friendsUsernames );
}

Resources