Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS

How to get another parse.com class with query (xcode / ios) - based on the build a self destructing iphone app project

I have been struggling with this for a few days now and thought I would make use of here to try and find a solution / some guidance.

I have a query that gets back the 'friends' of the currently logged in user. That works fine.

I have added another class that stores a 'check-in' for each of the users.

I am trying to bring back the latest / last added check-in for each of the returned 'friends' so I can display it.

I am able to hard code a query to get the check-ins. But I can't work out how to only bring back the check-ins of the friends, and then in-turn only bring back the latest one for each of them.

In essence I want to modify the code that brings back the current users friends and get it to also include the latest check-in for each of those friends so I can include it in the UITable

Below is some of the code I have been trying to use to do this. I keep trying to go through the documentation and similar questions I have found to work this out but I am at a loss.

Any help would be appreciated. :)

Please do ask for any clarifications etc that may help me better present my issue. :)


     ////////////////////////////////////////////////////////////////////////
    // This was used as a test to get a basic hard coded query working         \/
    ////////////////////////////////////////////////////////////////////////

//    // which class (aka table) do we want to check on
//    PFQuery *querylocations = [PFQuery queryWithClassName:@"LocationStatus"];
//   
//    //how do we want to order the data that comes back?
//    [querylocations orderByAscending:@"username"];
//   
//     //what 'field' do we want to check on and what should the value be?
//    [querylocations whereKey:@"username" equalTo:[PFUser currentUser]];
//    [querylocations findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
//        if (!error) {
//             //The find succeeded.
//            NSLog(@"Successfully retrieved %d locations.", objects.count);
//             //Do something with the found objects
//        for (PFObject *object in objects) {
//                NSLog(@"%@", object);
//        }
//       } else {
//            // Log details of the failure
//            NSLog(@"Error: %@ %@", error, [error userInfo]);
//        }
//    }];

    ////////////////////////////////////////////////////////////////////////
    // This was used as a test to get a basic hard coded query working         /\
    ////////////////////////////////////////////////////////////////////////








    self.friendsRelation = [[PFUser currentUser] objectForKey:@"friendsRelation"];
    PFQuery *query = [self.friendsRelation query];
    [query orderByAscending:@"firstname"];
    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    if (error) {
            NSLog(@"Error %@ %@", error, [error userInfo]);
            }
        else {

            //log the objects that we got back to the NSLOG
            NSString *theusers = [objects valueForKey:@"objectId"];
            NSLog(@"%@", theusers);
            NSLog(@"%@", objects);

            // get the objects that were returned and send them to the view we are in (aka self)
            self.friends = objects;

            //reload the table as we now have the objects / data
            [self.tableView reloadData];

        }
    }];

Ok, quick question before I go too far on this...

Is it possible to merge the friends query into the main one so I can use all the variables etc in a single array? So at the moment it does the call to parse, gets back the users, and I can echo out their name, username etc. Can I combine into that the latest status so I can just write out the 'status' too as it is just part of the single array? Or is that what the code you've presented actually is doing?

I think I have answered my own question. I am now really close to being done with this. I have the following code. I can now see the latest status coming up in the log - but I am unsure how to grab it into a variable and print it out in the table cell as I intend.

I tried putting it into an NSString object like so but that doesn't work.

Code at top getting the info.

self.friendsRelation = [[PFUser currentUser] objectForKey:@"friendsRelation"];
PFQuery *query = [self.friendsRelation query];
[query orderByAscending:@"firstname"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    if (error) {
        NSLog(@"Error %@ %@", error, [error userInfo]);
    }
    else {

        //log the objects that we got back to the NSLOG
        NSString *theusers = [objects valueForKey:@"objectId"];
        NSLog(@"%@", theusers);
        NSLog(@"%@", objects);

        // get the objects that were returned and send them to the view we are in (aka self)
        self.friends = objects;


        //now that we have friends we also want to get all those friends check ins
        for (PFUser *friend in self.friends) {

            PFRelation *checkInsRelation = [friend relationForKey:@"checkIns"];
            PFQuery *checkInsQuery = [checkInsRelation query];
            [checkInsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

                if (error) {
                    NSLog(@"Error %@ %@", error, [error userInfo]);
                }
                else {
                    NSString *thestatus = [objects valueForKey:@"objectId"];
                    NSLog(@"%@", thestatus);
                    NSLog(@"%@", objects);

                //do what you want this friends check ins


                }

                //reload the table as we now have the objects / data
                [self.tableView reloadData];

            }];
        }

    }
}];

Later code to place into the tablecell

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    PFUser *user = [self.friends objectAtIndex:indexPath.row]; NSString *firstname = user[@"firstname"]; NSString *lastname = user[@"lastname"]; NSString *lateststatus = user[@"locationName"];

    // Show the username in the top of the cell view cell.textLabel.text = [NSString stringWithFormat:@"%@ %@ ", firstname, lastname];

    // show the status in the subtitle of the list view - for now we show the username until we can get it working with their location cell.detailTextLabel.text = lateststatus;

    return cell; }

post a new question, this one has a bit too many posts so its gettting confusing

Thank you I have done so.

For anyone who may follow this in the future I posted it here: https://teamtreehouse.com/forum/trouble-extracting-data-from-database-query-from-parse-with-a-pfrelation

6 Answers

sounds like a good case for using a PFRelation for that check in class you have. you could add a relation to your user class called "hasCheckIns" or whatever, then whenever you query for the users friends you could then query each one of those friend's hasCheckins relation (limit the return to 10 objects or so) and that would give you all the check ins for each one of the users friends.

so whenever you create a check in object, that check in to the relation and save the current user object:

PFRelation *checkInsRelation = [[PFUser currentUser] relationForKey:@"checkIns"];
[relation addObject:checkIn];
[[PFUser currentUser] saveInBackground];

then where ever you query for friends:

self.friendsRelation = [[PFUser currentUser] objectForKey:@"friendsRelation"];
    PFQuery *query = [self.friendsRelation query];
    [query orderByAscending:@"firstname"];
    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    if (error) {
            NSLog(@"Error %@ %@", error, [error userInfo]);
            }
        else {

            //log the objects that we got back to the NSLOG
            NSString *theusers = [objects valueForKey:@"objectId"];
            NSLog(@"%@", theusers);
            NSLog(@"%@", objects);

            // get the objects that were returned and send them to the view we are in (aka self)
            self.friends = objects;


            //reload the table as we now have the objects / data
            [self.tableView reloadData];

             //now that we have friends we also want to get all those friends check ins
            for (PFUser *friend in self.friends) {

                          PFRelation *checkInsRelation = [friend relationForKey:@"checkIns"];
                          PFQuery *checkInsQuery = [checkInsRelation query];
                          [checkInsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
                                   //do what you want this friends check ins
                          }];
            }

        }
    }];

I considered that. At the moment I have the checkins being stored / submitted by a user. On the checkins class I have a pointer to the user that owns that checkin.

Would I be better off changing the way I submit and store the user checkins to use a relationship instead?

One user can have many checkins. While only the most recent one is relevant for now.

I would probably use a PFRelation. I updated my answer to show some code. I could be way off though and there might be a better solution, but thats what came to mind first.

Thank you. I wasn't sure if the pointers would be better. Seeing as I have already worked with relations it does appear logical to do the same again and try refactoring the code I have worked with already.

I appreciate the time you've put in to answering my question. I'll have a further go and re-working my code and see if I can solve the issue.

After that, I need to start looking at how I even begin to only show friends that people have in their contacts book on their device rather than all users on the database ... but that's another task entirely :)

Thank you again for your time, I'm still having issues getting the formatting of my code right. Would you be able to help me work through this?

    // Create the checkin object ready to put into parse.com
    PFObject *checkIns = [PFObject objectWithClassName:@"checkIns"];
    checkIns[@"locationName"] = location;
    checkIns[@"roughteta"] = rougheta;


    // Create relationship between the checkin and the current user
    PFRelation *checkInsRelation = [[PFUser currentUser] relationforKey:@"checkIns"];


    // Save the new location status
    [checkIns saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (!error) {

At the moment I get an error on the PFRelation line saying I have an unused variable of 'checkInsRelation' and I'm not clear on where I need to then use that..

IIRC you have to save any objects first before you can add them to a relation, so you will have to add your PFRelation stuff in the success block where you save the checkInsObject first. You are getting an warning about htat unused variable because you forgot to add the checkIns Object to the relation and save the current user. so in your success block of your checkIns saveInBackGround method add

PFRelation *checkInsRelation = [[PFUser currentUser] relationForKey:@"checkIns"];
[relation add:checkIn];
[[PFUser currentUser] saveInBackground];

Me again. Sorry. Just coming back into this now.

I can't work out why I am now getting an error trying to use 'relation' on the [relation add:checkIn];

The error is Use of undeclared identifier: relation

    // Create the checkin object ready to put into parse.com
    PFObject *checkIns = [PFObject objectWithClassName:@"checkIns"];
    checkIns[@"locationName"] = location;
    checkIns[@"roughteta"] = rougheta;



    // Save the new location status
    [[PFUser currentUser] saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (!error) {


            // Create relationship between the checkin and the current user
            PFRelation *checkInsRelation = [[PFUser currentUser] relationForKey:@"checkIns"];
            [relation add:checkIn];
            [[PFUser currentUser] saveInBackground];


            // lets show an alert to confirm that they were checked in ok
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Looking Good!" message:[NSString stringWithFormat:@"You were successfully checked-in! Your friends will know you are until %@ from now :)", rougheta] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
             [alertView show];

            // let's redirect them straight back to their friends view
             [self.tabBarController setSelectedIndex:0];


        }

That's because your relation isn't named relation, it's named checkInsRelation. You need to use [checkInsRelation add:checkIn]

I tried that already but got this so presumed I was heading off on the wrong track.

http://i.imgur.com/KnSUOYF.png

I apologise that I am having a hard time understanding this. I seem to be ok so far with everything else in the app, this is just tripping me up. Once I see it all working correctly I expect I'll have no issues in future as I will understand it in a concept / terms that make sense to me and my app :)

Again, I appreciate the time on this.

For reference, this is how my code now looks

    // Create the checkin object ready to put into parse.com
    PFObject *checkIns = [PFObject objectWithClassName:@"checkIns"];
    checkIns[@"locationName"] = location;
    checkIns[@"roughteta"] = rougheta;


    // Save the new location status
    [[PFUser currentUser] saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (!error) {

            // Create relationship between the checkin and the current user
            PFRelation *checkInsRelation = [[PFUser currentUser] relationForKey:@"checkIns"];
            [checkInsRelation add:checkIn];
            [[PFUser currentUser] saveInBackground];


            // lets show an alert to confirm that they were checked in ok
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Looking Good!" message:[NSString stringWithFormat:@"You were successfully checked-in! Your friends will know you are until %@ from now :)", rougheta] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
             [alertView show];

            // let's redirect them straight back to their friends view
             [self.tabBarController setSelectedIndex:0];

And if I correct that to CheckIns instead of CheckIn I get

http://i.imgur.com/AsNJ6WH.png

etc

Try using addObject instead of add

Thank you. That removed all code validation errors. Now I am finding that when I 'save' the app crashes on the phone.

{ @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([outoutAppDelegate class])); }

are you getting any other errors in the console?

Ahhh yes I am

2014-04-07 18:06:27.128 OutOut[13391:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'All objects in a relation must have object ids.' *** First throw call stack: ( 0 CoreFoundation 0x029651e4 exceptionPreprocess + 180 1 libobjc.A.dylib 0x026e48e5 objc_exception_throw + 44 2 CoreFoundation 0x02964fbb +[NSException raise:format:] + 139 3 OutOut 0x00034078 +[PFRelationOperation addRelationToObjects:] + 573 4 OutOut 0x00043ffd -[PFRelation addObject:] + 105 5 OutOut 0x00003303 __37-[CheckinViewController savecheckin:]_block_invoke + 332 6 OutOut 0x00056c34 __53-[PFTask thenCallBackOnMainThreadWithBoolValueAsync:]_block_invoke + 98 7 OutOut 0x000569b7 __40-[PFTask thenCallBackOnMainThreadAsync:]_block_invoke_2 + 241 8 libdispatch.dylib 0x0398a7b8 _dispatch_call_block_and_release + 15 9 libdispatch.dylib 0x0399f4d0 _dispatch_client_callout + 14 10 libdispatch.dylib 0x0398d726 _dispatch_main_queue_callback_4CF + 340 11 CoreFoundation 0x029ca43e __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE + 14 12 CoreFoundation 0x0290b5cb __CFRunLoopRun + 1963 13 CoreFoundation 0x0290a9d3 CFRunLoopRunSpecific + 467 14 CoreFoundation 0x0290a7eb CFRunLoopRunInMode + 123 15 GraphicsServices 0x037305ee GSEventRunModal + 192 16 GraphicsServices 0x0373042b GSEventRun + 104 17 UIKit 0x013a4f9b UIApplicationMain + 1225 18 OutOut 0x00004a04 main + 94 19 libdyld.dylib 0x03bd4701 start + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException

Fox Geere You are getting that error because the check in object wasnt saved before being added to the relation, it doesnt have an object id because it wasnt saved to the backend. you need to save the check in before adding it to the relation. youll need to edit your code a bit like so:

// Save the new check in object and then add it to the relation before saving the user
    [checkIn saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (!error) {

            // Create relationship between the checkin and the current user
            PFRelation *checkInsRelation = [[PFUser currentUser] relationForKey:@"checkIns"];
            [checkInsRelation add:checkIn];
            [[PFUser currentUser] saveInBackground];


            // lets show an alert to confirm that they were checked in ok
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Looking Good!" message:[NSString stringWithFormat:@"You were successfully checked-in! Your friends will know you are until %@ from now :)", rougheta] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
             [alertView show];

            // let's redirect them straight back to their friends view
             [self.tabBarController setSelectedIndex:0];

Thank you SO much! I had to tweak it slightly (addObject, CheckIns etc) but it is now working! :) Going to comment the code further for future reference too. :D

Next step, working on the part that will bring the latest checkin back out for each user. You gave me some sample code the other day so I'll review that and see if I can work it out. If not, I'll be sure to start a seperate question so things don't get messy here.

THANK YOU SO MUCH again!

Fox