Set PFQuery order - ios

I want to make my PFQuery come in a random order, so the next time I'm creating the same PFQuery with limit it won't return the same objects as the first one.
PFQuery *query = [PFUser query];
[query orderBy...]; //Is there a randomOrder method?
//Or a workaround to get random order?
[query setLimit:10];
I need this to be in a random order every time, or else the PFQuery will contain the same 10 objects everytime

You can't change the ordering of data returned in the query, but you can use paging to change the first object that is returned - so you could do something like this (it is based on the ToDo sample code from Parse but it will work for any object) -
PFQuery *query =[PFQuery queryWithClassName:#"Todo"];
NSInteger count=[query countObjects];
NSInteger skip = arc4random_uniform(count-10);
query.skip=skip;
query.limit=10;
NSArray *results=[query findObjects];
NSLog(#"object count=%d",results.count);
for (PFObject *object in results) {
NSLog(#"text=%#",object[#"text"]);
}
You can now retrieve your 10 objects. for any given skip count they will be in the same order, but you could randomise the order after you retrieved the 10 items. Simply put them into an NSMutableArray and use technique in this answer - Re-arrange NSArray/MSMutableArray in random order
Note that this code isn't optimal as it doesn't perform the fetch tasks on the background thread. To use background threads you would use something like the following -
PFQuery *query =[PFQuery queryWithClassName:#"Todo"];
[query countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
query.skip=arc4random_uniform(number-10);;
query.limit=10;
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"An error occurred - %#",error.localizedDescription);
}
else {
NSLog(#"object count=%d",objects.count);
for (PFObject *object in objects) {
NSLog(#"text=%#",object[#"text"]);
}
}
}];
}];

PFQuery does not support random order but you can workaround this by creating an increasing index field to each object
Then given that you know the maxIndexin the table you can generate random indices as the following:
- (NSArray *)generateRandomIndices:(int)maxIndex limit:(int)limit {
NSMutableArray *indices = [[NSMutableArray alloc] initWithCapacity:limit];
for (int i=0; i<limit; i++) {
int randomIndex = arc4random() % maxIndex;
[indices addObject:[NSNumber numberWithInt:randomIndex]];
}
return indices;
}
Now you can query your class by using INpredicate
NSArray *randomIndices = [self generateRandomIndices:maxIndex limit:10];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"index IN %#", randomIndices];
PFQuery *query = [PFQuery queryWithClassName:#"className" predicate:predicate];

PFQuery don't give random objects. You can get all objects then randomize to get any 10 objects from it and show it.

Related

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.

What is the way in Parse (iOS) to get a query key value other than the id?

I am currently using parse to develop an iPhone app and I got this code:
I have a Class called "Event" and has a field called "EventName"
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query selectKeys:#[#"EventName"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %lu scores.", (unsigned long)objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
}
}
}];
This works perfectly, but i get the object's IDs instead of the event names which is what i want.
EDIT :
So far I got this inside the IF statement
self.eventArray = [objects valueForKey:#"EventName"];
for(int i=0; i<10 ; i++){
NSLog(#"%#", eventArray[i]);
}
by doing that the question got solved.
All you have to do is use the objects array that was retuned from finding the objects and pass them into your array by using valueForKey.
So it would be
if (!error) {
self.yourEventNameArray = [objects valueForKey:#"EventName"];
}

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 );
}

How to fetch a PFObject with all it's fields and related objects in one go?

I have an NSArray of PFObjects and want to fetch all data related to the objects in this array in one go, so afterwards there is no need to make a new call to parse. How can I do this?
My answer is assuming the array you want is contained in a PFObject. You can query for this object and use the include key to include the array contained within that key.
PFQuery *query = [PFQuery queryWithClassName:#"<object's class name>"];
[query whereKey:#"objectId" equalTo:object.objectId];
[query includeKey:#"<array key>"];
If the objects in your array have pointers to other objects within them you can use the dot syntax to fetch everything at once.
[query includeKey#"<array key>.<pointer in object from array key>"];
Run the query once set up and you should retrieve an array of one object since objectIds are unique, within this object will be the array.
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
if(error){
// handle error
}else{
if([objects count] > 0){
PFObject *object = objects[0]; // Only one matching object to query
NSArray *array = object[#"<array key>"]; // Array you want
}
}
}];
You can use PFObject's methods family:
+(void)fetchAll:(NSArray*)objects
Check out PFObject documentation on those methods https://parse.com/docs/ios/api/Classes/PFObject.html#//api/name/fetchAll:
As far I got your query , this might be-
PFQuery *query = [PFQuery queryWithClassName:#"CLASS_NAME"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error && objects.count != 0)
{
NSLog(#"Successfully retrieved: %#", objects);
for(int i=0; i< objects.count; i--)
{
NSDictionary *dict = [objects objectAtIndex:i];
self.label = [dict objectForKey:#"PROPERTY_KEY"]; //Just example
/* Also modify this code as per you want to fetch properties of some or all */
/* Do as you want to with properties and also change key as per need of column/property you want */
}
}
}];
This way you will get array and also fetch fetch as you want.

Remove an item from a column type of array in Parse (iOS SDK)

I have table name "Events". In that table I have a column of type array of string. I'm struggling with how I can delete only one element from that column. Consider the image below, I want to delete all the occurrences of "iYYeR2a2rU" from the "usersIncluded" column, without deleting the rows.
I've used the removeObject:(id) forKey:(NSString *) and it didn't work.
This is how I'm trying to achieve it:
PFQuery *query = [PFQuery queryWithClassName:#"Events"];
NSArray *eventObjects = [query findObjects];
[query whereKey:#"usersIncluded" equalTo:[self.uniqeFriendList objectAtIndex:indexPath.row]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
for (int i = 0; i<objects.count; i++) {
PFObject *event = [eventObjects objectAtIndex:i];
[event removeObject:[self.uniqeFriendList objectAtIndex:indexPath.row] forKey:#"usersIncluded"];
}
}];
}
The self.uniqeFriendList is a mutable array containing the ids that I want to delete from the 'usersIncluded' column.
Thanks in Advance
I think you're using the right method (removeObject:forKey: should do exactly what you want) but I think you're working with objects from the wrong array. You're performing your query twice, and within the findObjectsInBackgroundWithBlock: you're working with the array from the first time you called it... Try this:
PFQuery *query = [PFQuery queryWithClassName:#"Events"];
[query whereKey:#"usersIncluded" equalTo:[self.uniqeFriendList objectAtIndex:indexPath.row]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
for (int i = 0; i <objects.count; i++) {
PFObject *event = [objects objectAtIndex:i]; // note using 'objects', not 'eventObjects'
[event removeObject:[self.uniqeFriendList objectAtIndex:indexPath.row] forKey:#"usersIncluded"];
}
[PFObject saveAll:objects];
}];
}

Resources