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

Trouble extracting data from database query from parse with a PFRelation

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 NSlog of objects - 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"];


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

I have a hunch that this isn't quite right. I am getting 'all' the status back for each user rather than just the latest one, and I don't feel like the status correlate to each of the users that own them.

I'm still playing with my code trying to work this all out though :)

Obviously any help is appreciated. :)

7 Answers

ok your gonna need to modify that code a bit. Instead of getting the checkIns relation directly after obtaining your friends, your gonna get your checkIns for each friend in cellForRowAtIndexPath. So remove this

//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];

                }];
            }

and in your cellForRowAtIndexPath add

- (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];

    //get the check ins for this user
    PFRelation *checkInsRelation = [user relationForKey:@"checkIns"];
    PFQuery *checkInsQuery = [checkInsRelation query];
    [checkInsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

                    if (error) {
                        NSLog(@"Error %@ %@", error, [error userInfo]);
                    }
                    else {

                        //weve got our checkIn objects, lets get the statusContent for the first checkIn (most recent) in the array
                        NSString *latestStatus = [[objects objectAtIndex:0] objectForKey@"statusContent"]

                    }

     }];


    NSString *firstname = user[@"firstname"];
    NSString *lastname = user[@"lastname"];


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

Now, im not sure how your checkIns data is organized, in the code above I assumed you have a field called "statusContent" in your checkIn class, but thats probably not the case so you will just need to change that to whatever field you have stored the status content you want displayed in

Thank you. Will try all this now :)

I had to do a bit of adjusting to get it to start validating (was missing a : and a ; here any there).

I now have the code at the end of this comment. However I also have an error at this line

 //weve got our checkIn objects, lets get the statusContent for the first checkIn (most recent) in the array
            NSString *latestStatus = [[objects objectAtIndex:0] objectForKey:@"locationName"];

saying that latestStatus is an unused variable and when I run the code it crashes the app with the log error:

** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'

So my code at present is as follows.

- (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];

    //get the check ins for this user
    PFRelation *checkInsRelation = [user relationForKey:@"checkIns"];
    PFQuery *checkInsQuery = [checkInsRelation query];
    [checkInsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

        if (error) {
            NSLog(@"Error %@ %@", error, [error userInfo]);
        }
        else {

            //weve got our checkIn objects, lets get the statusContent for the first checkIn (most recent) in the array
            NSString *latestStatus = [[objects objectAtIndex:0] objectForKey:@"locationName"];

        }

    }];


    NSString *firstname = user[@"firstname"];
    NSString *lastname = user[@"lastname"];


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

that probably happened because not everybody has at least one checkIn, add a condition to account for that

 else {

           //make sure the user actually has some checkIns
           if (objects.count > 0) {

            //weve got our checkIn objects, lets get the statusContent for the first checkIn (most recent) in the array
            NSString *latestStatus = [[objects objectAtIndex:0] objectForKey:@"locationName"];

        }

}

you are also gonna need to declare that latestStatus variable outside of that if else so its within scope

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

    NSString *latestStatus; 

and instead of declaring it inside the if else just assign it your value:

            //weve got our checkIn objects, lets get the statusContent for the first checkIn (most recent) in the array
            latestStatus = [[objects objectAtIndex:0] objectForKey:@"locationName"];

        }

Ok so I worked the code in. It said it needed to be declared as a BLOCK so I did that higher up.

If I echo out the variable in the log it comes out however it doesn't show up in the detail label or the 'latest status 2' NSlog that I added

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

    NSString __block *latestStatus;

    PFUser *user = [self.friends objectAtIndex:indexPath.row];

    //get the check ins for this user
    PFRelation *checkInsRelation = [user relationForKey:@"checkIns"];
    PFQuery *checkInsQuery = [checkInsRelation query];
    [checkInsQuery orderByDescending:@"createdAt"];
    [checkInsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

        if (error) {
            NSLog(@"Error %@ %@", error, [error userInfo]);
        }
        else {

            //make sure the user actually has some checkIns
            if (objects.count > 0) {

                //weve got our checkIn objects, lets get the statusContent for the first checkIn (most recent) in the array
                latestStatus = [[objects objectAtIndex:0] objectForKey:@"locationName"];
                NSLog(@"latest status: %@", latestStatus );

            }

        }

    }];

    NSLog(@"latest status 2 : %@", latestStatus );
    NSString *firstname = user[@"firstname"];
    NSString *lastname = user[@"lastname"];

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

Ok. Sorted it by moving the closing parts of the code :)

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

    NSString __block *latestStatus;

    PFUser *user = [self.friends objectAtIndex:indexPath.row];

    //get the check ins for this user
    PFRelation *checkInsRelation = [user relationForKey:@"checkIns"];
    PFQuery *checkInsQuery = [checkInsRelation query];
    [checkInsQuery orderByDescending:@"createdAt"];
    [checkInsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

        if (error) {
            NSLog(@"Error %@ %@", error, [error userInfo]);
        }
        else {

            //make sure the user actually has some checkIns
            if (objects.count > 0) {

                //weve got our checkIn objects, lets get the statusContent for the first checkIn (most recent) in the array
                latestStatus = [[objects objectAtIndex:0] objectForKey:@"locationName"];
                NSLog(@"latest status1: %@", latestStatus );

            }

        }



    NSLog(@"latest status 2 : %@", latestStatus );
    NSString *firstname = user[@"firstname"];
    NSString *lastname = user[@"lastname"];

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

alright so its working now?

Just doing some testing, but yes, appears everything is working now. Thank you so much for your help! :)

I am noticing the tableview loads with 'dummy' data for a moment before it gets refreshed with the correct / real data. Any inside notes on that off the top of your head and a way to avoid it? :) No worries if not.

where is the dummy data coming from? ive never seen that happen before before

It says 'title' and 'sub title' for a while until the data loads in. Perhaps a way to just edit what these say so they say 'loading' or something.

Sorted. Just had to edit the prototype cell :)

I have never seen that before. It might be something in your code where you set those properties for your tableView in viewDidLoad or something. But the tableView wouldnt do that by itself I dont think.

Is there a way to group these so that those who DO have a status would be first in the table and the rest that don't currently have a status are displayed underneath in a separately headed section?

// maybe I should go do some reading rather than keep asking :)

im sure there is a way, however im not sure how. sorry. You could probably modify your query that gets users a bit to find users whose CheckInsRelation isnt null (that would mean that they at least have 1 checkIn). But im not sure what you would do about the other people and multiple sections etc