I can't understand why my query is not working - objective-c

so i have a bunch of objects on parse.com. class name is "MainInfo" with geo points in a column named geoPoint.
I am getting the users location by adding the following to my .h file:
#property (nonatomic, strong) PFGeoPoint *userLocation;
Then adding the following to viewDidLoad:
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError *error) {
if (!error) {
self.userLocation = geoPoint;
[self loadObjects];
}
}];
and performing the rolling queryForTable:
- (PFQuery *)queryForTable
{
// User's location
PFGeoPoint *userGeoPoint = self.userLocation;
// Create a query for places
PFQuery *query = [PFQuery queryWithClassName:#"MainInfo"];
// Interested in locations near user.
[query whereKey:#"geoPoint" nearGeoPoint:userGeoPoint];
// Limit what could be a lot of points.
query.limit = 10;
// Final list of objects
_placesObjects = [query findObjects];
return query;
}
Xcode gives me the error *** setObjectForKey: object cannot be nil (key: $nearSphere)
I've got no idea what I am doing wrong, as far as I can see it should work.
I worked along with the parse documentation to get me this far. Here is a link

When you make the geoPointForCurrentLocationInBackground call it has a completion block. This completion block marks the point at which you have the required information to populate the table view (or you know that there is an error and you should do something else). So, you shouldn't display / load query data into the table view until the completion block is called. Other wise you don't have the required information to complete the query.
You could display an activity indicator while you're waiting. Or, it might be better to get the userLocation before even showing this view so you always have the information for the query when you get here.

The error arises because you're passing a nil value to whereKey:nearGeoPoint: as self.userLocation is unlikely to be set the first time the view loads. You will want to do two things:
In your queryForTable method, check if self.userLocation is nil. If it is, return nil. This acts as a no-op and the table won't show any data just yet.
- (PFQuery *)queryForTable
{
if (!self.userLocation) {
return nil;
}
// User's location
PFGeoPoint *userGeoPoint = self.userLocation;
// Create a query for places
PFQuery *query = [PFQuery queryWithClassName:#"MainInfo"];
// Interested in locations near user.
[query whereKey:#"geoPoint" nearGeoPoint:userGeoPoint];
// Limit what could be a lot of points.
query.limit = 10;
// Final list of objects
_placesObjects = [query findObjects];
return query;
}
In your geoPointForCurrentLocationInBackground: completion block, once the self.userLocation value is set, you will want to call [self loadObjects]. This will tell the PFQueryTableViewController to run your query again, and this time around self.userLocation will not be nil, allowing you to construct your original query. Fortunately, you've already performed this step, but I've included it here in case anyone else has the same question.

Related

PFQuery's hasCachedResults always returns NO, even if it ends up using a cached result

I use a query to fill in a value on a cell and cache it to avoid making multiple network calls when cell is reused. I animate a value in based off the response of the query.
No matter what I try I can't get the animation to only occur if its a network response. I know I'm getting cached results because the value animates instantly any time I scroll to that cell after the first time. (I also set it up to cache anyway, but this corroborates that I did it correctly).
Is there a special way you are supposed to be using hasCachedResults?
NOTE: It has nothing to do with the fact that I'm using objectWithoutData and doing a relationship query. See link below under EDIT for more information
Here's what my code looks like:
PFObject *obj = [PFObject objectWithoutDataWithClassName:...];
PFRelation *relation = [obj relationForKey:...];
PFQuery *q = [relation query];
[q whereKey:...];
// CHECKING TO SEE IF CACHED
if ([q hasCachedResult]) {
NSLog(#"1");
}
q.cachePolicy = kPFCachePolicyCacheElseNetwork; // to prevent network calls every time cell is re-used.
// CHECKING TO SEE IF CACHED
if ([q hasCachedResult]) {
NSLog(#"2");
}
[q countObjectsInBackgroundWithBlock:^(int number, NSError * _Nullable error) {
// CHECKING TO SEE IF CACHED
if ([q hasCachedResult]) {
NSLog(#"3");
}
}
Neither 1, 2, or 3 get printed.
EDIT
This may be a bug (or at least unclear documentation). I looked into it a little more, I included a lot more information here: Issue on Github

Parse.com combine nested query

I am new to using parse, and currently adding parse to an app that uses SOAP web services to replace them.
Now I come a little stuck, as due to having it done in MySQL and php I am trying to translate the logic from the tables and code to parse.
I have a function that I have written and it achieves exactly what I am after, however I just think its wrong and can be done better, for starters I am not calling findObjectsInBackground, which I know I need to.
I am trying to take this result and then reload a tableView.
NSMutableArray *activityfeed = [NSMutableArray new];
//get current user
PFUser *user = [PFUser currentUser];
if (user != nil) {
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
//get event that user is involved in
[query whereKey:#"invited" equalTo:user];
//get the events
NSArray *events = [query findObjects];
//loop over the events
for (PFObject *event in events) {
//owner is a pointer to the users class
PFUser *owner = event[#"owner"];
if (![user.objectId isEqualToString:owner.objectId]) {
//ignore the info for the logged in user for now
//invited is a relation so one event has many users
PFRelation *relation = [event relationForKey:#"invited"];
PFQuery *query = [relation query];
[query orderByDescending:#"dateinvited"];
[query addDescendingOrder:#"dateaccepted"];
//get the friends that are involved with the event
NSArray *friends = [query findObjects];
for (PFUser *friend in friends) {//Perform logic checks here and then add to activityfeed}
}
}
}
return activityfeed;
So my logic above gets the current user, then get all the events that user is involved with, then get all the other people involved with that event and then work out what to display.
Is there a more efficient way of doing the above?
Usually we use whereKey:matchesQuery in cases like this. Take some research on this.
This question may help you find out some solution.

Correct way to user PFQuery to query relational data like Pointer

Ok so let's say I have a post of an event, and users can click a button to notify that they are attending this event. As of now I have a class called Activity in which I save the current user and the event to this class, so theres 2 columns. If I want to query all users who are attending an event, am I headed in the right direction to do this, or am I doing it complentely wrong?
So far I have:
-(PFQuery*)queryForTable {
PFQuery *activityQuery = [PFQuery queryWithClassName:#"Activity"];
[activityQuery whereKey:#"event" equalTo:self.event];
[activityQuery includeKey:#"going"];
return activityQuery;
}
cellForRowAtIndex:
UILabel *title = (UILabel*) [cell viewWithTag:1];
title.text = [object objectForKey:#"going.username"];
You can actually see what you have been done in the Parse dashboard. That's also their purpose to develop a data browser like this. It's way more convenient.
For your case, you just need to check whether the type is Pointer. Try to click on that if so in the dashboard. It will direct you to the target object.
Would suggest you to read this article first, it's about the relation:
https://parse.com/docs/relations_guide
Then, you should go check the iOS SDK tutorial:
includeKey is definitely what you need to use.
Here is the sample from Parse:
PFQuery *query = [PFQuery queryWithClassName:#"Comment"];
// Retrieve the most recent ones
[query orderByDescending:#"createdAt"];
// Only retrieve the last ten
query.limit = 10;
// Include the post data with each comment
[query includeKey:#"post"];
[query findObjectsInBackgroundWithBlock:^(NSArray *comments, NSError *error) {
// Comments now contains the last ten comments, and the "post" field
// has been populated. For example:
for (PFObject *comment in comments) {
// This does not require a network access.
PFObject *post = comment[#"post"];
NSLog(#"retrieved related post: %#", post);
}
}];
Your code looks right on so far. Then to retrieve your Activity class values, you can use:
PFQuery *activityQuery = [PFQuery queryWithClassName:#"Activity"];
// Set contraints here, example:
[activityQuery setLimit:100];
[query findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error) {
if (!error) {
// Success, do something with your objects.
}
}];

parse.com getting pointer data from currentUser

i have a pointer key in PFUser and I'm trying to retrieve the object it's pointing to. I've seen many examples about querying for it but there shouldn't be any need for that, since parse has the fetch method and it's a pointer of PFUser class, so I'm using this:
PFObject *menu = [[[PFUser currentUser] objectForKey:#"menuID"] fetchIfNeeded];
I know my current user has an object being pointed to in that key but i get a null menu object all the time
Wain was correct in saying that you need to fetch the currentUser. However, you have to keep in mind that we're working with multiple threads if you want to use fetchInBackground. To keep in on a single thread, simply use [[PFUser currentUser] fetch], but keep in mind that this can cause hanging or blocking for your user when internet connection is bad. Below is a sample of how to use this more effectively. (Same issue with the fetch vs. fetchInBackground for the menu) We have to fetch the menu as well, since it is a pointer and so the currentUser will only fetch the pointer and not the whole object.
[[PFUser currentUser] fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(!error){
PFObject *menu = object[#"menuID"];
[menu fetch];
// Execute any code that needs the menu here, or store it in your viewController (or other class variable) if you need to save it for later
// Alternately, you could use this:
dispatch_async(dispatch_get_main_queue(), ^{
[menu fetchInBackgroundWithBlock:^(PFObject *fetchedMenu, NSError *menuError) {
if(!menuError){
// Execute any code that needs the menu here, or store it
} else {
NSLog(#"%#", menuError);
}
}];
});
} else {
NSLog(#"%#", error);
}
}];
By default currentUser doesn't have any custom added columns of data populated. You need to fetch the user to download that data and then you can use it locally.
Alternatively your oils us cloud code and save a network request.

Parse query always true

Perhaps this is not the right place for this, but I am sure that many users here are familiar with Parse framework for iOS. Basically I am having issues with a query, all I want to do is check if a username already exists (they do so at login) except I need to do it in order to set up a relationship between the current user and another user. Currently my method is:
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:username.text];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSLog(#"query returned with result");
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
}
}
else {
NSLog(#"Nope");
}
}];
username is just a string containing the word entered in plain text from UITextField. But no matter what I put in, the query seems to go through and I get a message of success. I even tried iterating through the objects as shown in the for loop and I get nothing logged. What is going on here?
EDIT
Just for clarification, username is simply taken from a IBOutlet UITextField *username from the view controller which takes in the username from input. I have tested to make sure that it is being taken correctly. If I enter "foo", I can log the username.text and it will be "foo", however I have no registered users named "foo" so I do not understand why the query is returning without error.
You will get message of success even there is no object found.
(Try log objects.count)
If so, you should check your spelling on Key Value or Query Value.
They are case-sensitive.