Working on an App that allow user to see what local people have posted (businesses, schools, or just people). I am able to post the information and save it to parse. I am also able to download user data near current location, but it issue is that my helper method does not do it right away when the app first launches.
I have tried to call the helper method below in the viewDidLoad,viewWillAppear and viewDidAppear and no luck. The postArray is always null when the user initially opens the application and goes to the map. If I move to another screen and come back to the mapVC the data is there...(I NSlog the postArray) and all the posts near the current location print out. I would like to have this data initially right when the map presents the user's location.
Questions.
Where should I call my helper method? When the app launches I want to
have the data so i can display it on the map.
Is there another method that I need to write?
Is there something wrong with the current method.
- (void)loadLocalPosts {
NSLog(#"Querying for Local Posts");
[activityIndicator startAnimating];
PFQuery *query = [PFQuery queryWithClassName:#"Post"];
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError
*error) {
geopoint = geoPoint;
[query whereKey:#"location" nearGeoPoint:geoPoint];
[query setLimit:50];
[query addDescendingOrder:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
postArray = objects;
}];
}];
}
The problem here is that postArray is populated in a background thread and your application initializes faster than it can populate the array. This is common in asynchronous programming. The way to fix this, is by asking the map to refresh in the main thread.
- (void)loadLocalPosts {
NSLog(#"Querying for Local Posts");
[activityIndicator startAnimating];
PFQuery *query = [PFQuery queryWithClassName:#"Post"];
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError
*error) {
geopoint = geoPoint;
[query whereKey:#"location" nearGeoPoint:geoPoint];
[query setLimit:50];
[query addDescendingOrder:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
postArray = objects;
dispatch_async(dispatch_get_main_queue(),^
{
// update view properties, refresh etc.
});
}];
}];
}
Note: All view related modifications should always happen in the main thread. Hence the dispatch_get_main_queue(). Now the main loop could however, be doing view related operations, hence a synchronous call will crash the application. Hence dispatch_async is used. This will add the block to the next run loop to be executed after the current one.
Related
I have two tables TrendingUsers and Follow. Functionality required is like fetch users from TrendingUsers table and offer to follow, provided fetched user is not from user's follow list. If user is already get followed then skip.
Follow table has columns follower and leader.
PFQuery *followTableQuery = [PFQuery queryWithClassName:#"Follow"];
[followTableQuery whereKey:#"follower" equalTo:[PFUser currentUser] ];
[followTableQuery whereKey:#"leader" equalTo:#"fetchedUserObject" ];
[followTableQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
if (objects.count) {
//if following objects array will have single object
}
else
{
//not following to #"fetchedUserObject" user
}
}
}
];
This will confirm me that currentUser is following #"fetchedUserObject" user or not.
Now I want to integrate this to the TrendingUsers table query to fetch only such users that currentUser is not following.
You can simply use nested queries, the docs from Parse are usually a good starting point. Here is a sample code, from what I understood from your question, this should do the trick.
//This is our current user
PFUser *user = [PFUser currentUser];
//The first query, querying for all the follow objects from the current user
PFQuery *followingQuery = [PFQuery queryWithClassName:#"Follow"];
[followingQuery whereKey:#"follower" equalTo:user];
//Now we query for the actual trending users, but we do not want the query to return the users (who are in the #"leader" key) that have been found by the first query
PFQuery *trendingQuery = [PFQuery queryWithClassName:#"TrendingUsers"];
[trendingQuery whereKey:#"objectId" notEqualTo:user.objectId]; //don't return the current user
[trendingQuery whereKey:#"objectId" doesNotMatchKey:#"leader" inQuery:followingQuery]; //I'm supposing that #"leader" is containing the objectId of the specific user that is part of the follow object with the current user
[trendingQuery setLimit:1000];
[trendingQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
//...
}];
I may have not understood your data structure completely, so you may have to exchange one or more keys in the above code, but basically, this is how you would do this.
I have a view controller with inside table and I want to fill her with an array saved on Parse. To download the data I use this code:
PFQuery *query = [PFQuery queryWithClassName:#"myClass"];
[query whereKey:#"X" equalTo:#"Y"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(error==nil){
myArray=[object objectForKey:#"Z"];
NSLog(#"%#",myArray);
}
}];
}
Now I display it inside myarray the data on parse. But if I use arrays to populate the table it is always me empty. I used NSLog and I saw that outside of the method [query getFirstObjectInBackgroundWithBlock: ^ (PFObject * object, NSError * error) my array is always empty.
How can help me?
Fetching data from a remote database takes a little time. The parse functions that take block params run asynchronously. See the comments within your slightly modified code...
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(error==nil){
// this appears first in the file, but runs later
// after the request is finished
myArray=[object objectForKey:#"Z"];
NSLog(#"%#",myArray);
// tell our view that data is ready
[self.tableView reloadData];
}
}];
// this appears second in the file, but runs right away, right
// when the request is started
// while execution is here, the request isn't done yet
// we would expect myArray to be uninitialized
Be sure, in your datasource methods e.g. numberOfRows to answer myArray.count. And use the data in the array myArray[indexPath.row] when building the table view cell.
I am using Cloud Code in order to add two users to a chatRoom, but my app crashes when I call the Cloud Code function.
In my iOS viewController, a user clicks a button which calls the following method:
-(void)createChatRoom
{
PFQuery *queryForChatRoom = [PFQuery queryWithClassName:#"ChatRoom"];
[queryForChatRoom whereKey:#"user1" equalTo:[PFUser currentUser]];
[queryForChatRoom whereKey:#"user2" equalTo:self.giveItem.itemGiver];
PFQuery *queryForChatRoomInverse = [PFQuery queryWithClassName:#"ChatRoom"];
[queryForChatRoomInverse whereKey:#"user1" equalTo:self.giveItem.itemGiver];
[queryForChatRoomInverse whereKey:#"user2" equalTo:[PFUser currentUser]];
PFQuery *combinedChatRoomQuery = [PFQuery orQueryWithSubqueries:#[queryForChatRoom, queryForChatRoomInverse]];
[combinedChatRoomQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if ([objects count] == 0){
[PFCloud callFunctionInBackground:#"addUsersToChatRoom" withParameters:#{#"user1" : [PFUser currentUser]} block:^(id object, NSError *error) {
[self performSegueWithIdentifier:#"itemToChatSegue" sender:nil];
}];
};
}];
}
And here is the JavaScript function stored in my Parse Cloud.
Parse.Cloud.define("addUsersToChatRoom", function(request, response){
response.success("Jared this method call works, no implement it");
console.log("this is being logged to the console");
var user = response.body.user1;
console.log(user);
});
When I press the button to call this method, my app crashes and I get an output of errors that looks like this:
[PFInternalUtils encodeObject:allowUnsaved:allowObjects:]
[PFInternalUtils encodeObject:allowUnsaved:allowObjects:]
[PFCloud callFUnctionAsync:withParameters:]
[PFCloud callFunctionInBackground:withParameters:block:]
__36-[MyViewController createChatRoom]_block_invoke
__40-[PFTask thenCallBackOnMainThreadAsync:]_block_invoke_2
How can I move forward from this error?
Once the error is resolved, where will I be able to read the Cloud Code output? When I use curl to hit my app's Cloud Code (https://parse.com/docs/cloud_code_guide), I get the given "Hello world!" output, but in this case I'm calling the JS function from my objective-c code and I'm not sure where I can monitor my console output. (Ultimately, I will replace the console output that is currently in my JS function with the actual functionality I am looking for.)
How can I learn to make sense of this error-message output?
PFUser currentUser answers an object, but you must pass a dictionary of serializable types -- like the user id or username -- to callFunctionInBackground:withParameters:. Also, the cloud function should invoke either response.success() or response.error().
i need to get a list of f.e the 15 nearest users using my app. The current location of the current user is stored like this:
PFGeoPoint *currentLocation = [PFGeoPoint geoPointWithLocation:newLocation];
PFUser *currentUser = [PFUser currentUser];
[currentUser setObject:currentLocation forKey:#"location"];
[currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error)
{
NSLog(#"Saved Users Location");
}
}];
Now i'd like to retrieve the users nearby via PFQuery like so:
- (NSArray *)findUsersNearby:(CLLocation *)location
{
PFGeoPoint *currentLocation = [PFGeoPoint geoPointWithLocation:location];
PFQuery *locationQuery = [PFQuery queryWithClassName:#"User"];
[locationQuery whereKey:#"location" nearGeoPoint:currentLocation withinKilometers:1.0];
locationQuery.limit = 15;
NSArray *nearbyUsers = [locationQuery findObjects];
return nearbyUsers;
}
Unfortunately it won't work. My array seems to have no entries. Can somebody clear things up for me, how the use the query the right way ?
Cheers, David
(also posted at: https://www.parse.com/questions/pfquery-to-retrieve-users-nearby)
First a quick comment
The code for creating a geo point is a "long running process" you will probably see this appearing in the console as you are running it on the main thread. This means the app is blocked (frozen) until the geo point is returned.
You would be better off using the code...
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError *error) {
// Now use the geopoint
}];
This is the same for the findObjects query. You should be using...
[locationQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
// use the objects
}];
Actual answer
I imagine this is a read access issue. As you are accessing the User table which, by default, has no public read access.
Are you setting the default read access in the app delegate something like this...
PFACL *defaultACL = [PFACL ACL];
[defaultACL setPublicReadAccess:YES];
[PFACL setDefaultACL:defaultACL withAccessForCurrentUser:YES];
Also, maybe try loosening off the constraints. 1km is a very small radius to be checking.
Ah, something else I just spotted. [PFQuery queryWithClassName:#"User"]; is using the wrong class name.
It should be #"_User".
However, a better solution would be to use the class to generate the query...
PFQuery *userQuery = [PFUser query];
When you subclass the PFObject class properly it has this method which will generate the correct query for you.
I have created a new method which returns a BOOL, shown below.
+(BOOL)checkIfGameAlreadyExistsAgainst:(PFUser *)opponentUser {
// Find all the games where the current user is user1 and the opponentUser is user2
PFQuery *currentUserIsUser1 = [PFQuery queryWithClassName:#"Game"];
[currentUserIsUser1 whereKey:kMESGameUser1 equalTo:[PFUser currentUser]];
[currentUserIsUser1 whereKey:kMESGameUser2 equalTo:opponentUser];
[currentUserIsUser1 whereKey:kMESGameIsActive equalTo:[NSNumber numberWithBool:YES]];
[currentUserIsUser1 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects) {
// We have games where the current user is user1
// NEED TO RETURN NO TO THIS METHOD AND NOT RUN FURTHER IN METHOD...
NSLog(#"Results returned for existing game where current user is User1. Results: %#",objects);
} else {
// If there are no objects from first query and no error we run the second query
if (!error) {
// Find all the games where the current user is user2 and the opponentUser is user1
PFQuery *currentUserIsUser2 = [PFQuery queryWithClassName:#"Game"];
[currentUserIsUser2 whereKey:kMESGameUser1 equalTo:opponentUser];
[currentUserIsUser2 whereKey:kMESGameUser2 equalTo:[PFUser currentUser]];
[currentUserIsUser2 whereKey:kMESGameIsActive equalTo:[NSNumber numberWithBool:YES]];
[currentUserIsUser2 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects) {
// We have games where the current user is user2
// NEED TO RETURN NO TO THIS METHOD AND NOT RUN FURTHER IN METHOD...
NSLog(#"Results returned for existing game where current user is User2. Results: %#",objects);
}
}];
}
}
}];
return NO;
}
The problem I have is how to return a YES value within a block within the method.
See the sections in the method which say // NEED TO RETURN NO TO THIS METHOD AND NOT RUN FURTHER IN METHOD...
How can I return YES here. If I add return YES I get an incompatible pointer types error.
Further to this, once I have the method returning YES, how do I call this method and do something depending on the result.
For example I need to call this method and if it is true then do something else, if not do nothing...
I am not sure what you are asking, so here is a guess: you wish your block to return a value to checkIfGameAlreadyExistsAgainst.
When a block in constructed it usually makes a copy of any value referenced from its environment. If you wish your block to modify a variable in its environment you must mark that variable with __block. In your code this would look something like:
__block BOOL blockStatus = YES;
[currentUserIsUser1 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
...
blockStatus = NO;
}
];
if (!blockStatus)
{
...
}
Important: The name of the method you are calling, findObjectsInBackgroundWithBlock, suggests that the block may not be called synchronously, which means the call may return before the block is executed. If this is the case you need to tackle the issue in a different way; which may involve calling a synchronous equivalent of findObjectsInBackgroundWithBlock or modifying checkIfGameAlreadyExistsAgainst so that it accepts a block which it calls asynchronously with its result rather than returning a value directly.
HTH