ian ian - 4 months ago 13
Objective-C Question

how to get object by key and value

I have an object called masterMessages filled with objects called messages. Each message object has five keys:


  • objectId

  • senderId

  • senderName

  • messageBody

  • timestamp



Basically what I am doing now is querying all the messages sent to my user in this object called masterMessages. Then i'm using:

self.senderIds = [masterMessages valueForKeyPath:@"@distinctUnionOfObjects.senderId"];


to get all the different sender ids (senderId) in an array called senderIds. With this I will populate my conversations table. But i want to populate it with the sender names (senderName) and not the senderIds. I only do it this way in case two users have the same name.

I am trying to find:


  1. How do I say "get valueForKey:@"senderName" for this senderId



and


  1. is there a better way to populate my conversations table?



Here is my code:

note: im using parse.com

-(void)viewWillAppear:(BOOL)animated{

NSString *userId = [[PFUser currentUser] objectId];

PFQuery *query = [PFQuery queryWithClassName:@"lean"];
[query whereKey:@"recipientId" equalTo:userId];
[query orderByDescending:@"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(@"Error: %@ %@", error, [error userInfo]);
}
else {
// We found messages!
masterMessages = objects;

NSLog(@"self.messages = %@", masterMessages);

self.senderIds = [masterMessages valueForKeyPath:@"@distinctUnionOfObjects.senderId"];

NSLog(@"self.senderIds = %@", self.senderIds);


[self.tableView reloadData];

}
}];


}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self.senderIds count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

NSLog(@"self.senderIds = %@", self.senderIds);

NSString *senderDisplayName = [self.senderIds objectAtIndex:indexPath.row];

NSLog(@"sender = %@", senderDisplayName);


cell.textLabel.text = senderDisplayName;


return cell;
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
selectedId = [self.senderIds objectAtIndex:indexPath.row];


[self performSegueWithIdentifier:@"ShowMissionMessage" sender:self];



}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"ShowMissionMessage"]) {

[segue.destinationViewController setHidesBottomBarWhenPushed:YES];
MissionChat *missionchatviewcontroller = (MissionChat *)segue.destinationViewController;
missionchatviewcontroller.selectedId = selectedId;
missionchatviewcontroller.masterMessages = masterMessages;
}
}

Answer

There are a few issues in the question, one is how to dereference PFObjects, which we took care of on another thread. The rest of this question is about (a) how to use parse objects to build a datasource to support a tableview, and a harder one (b) how to get information from related objects.

Starting with (b), the harder one: There are a few ways to relate objects. Your choice a string-typed column containing the related object id, is intuitive (especially if you have an SQL background), but the least advisable. The better (best) way to model a one-to-one or small one-to-many relation is with a pointer (or array of pointers if one-to-many).

So I think your senderId and recipientId string columns should be replaced by pointer-typed columns called sender and recipient. The huge advantage of this is the ability to eagerly fetch those pointed-to objects on the message (or "lean" in your terms) query.

Having made that change, you're new improved query looks like this:

PFQuery *query = [PFQuery queryWithClassName:@"lean"];
// notice the first change for the better here:
[query whereKey:@"recipient" equalTo:[PFUser currentUser]];
[query orderByDescending:@"createdAt"];
// notice the really valuable feature here:
[query includeKey:@"sender"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

    // using the array of PFObjects understanding from your other question...

    for (PFObject *pfObject in objects) {
        NSString *messageBody = [pfObject objectForKey:@"messageBody"];
        // these lines here are the punch line:
        PFUser *sender = [pfObject objectForKey:@"sender"];
        NSString *senderName = [sender username];
        NSLog(@"The message %@ was sent by %@", messageBody, senderName);
    }
}];

The important thing to notice above is that we were able to ask resulting objects for the @"sender" column, and, because you've changed it to a pointer, and because you've done an includeKey on the query, that complete object (e.g. including the PFUser username) is now fetched.

Now the easy question (a). Now that you have the data right from the server, the datasource for the table is nothing more than the returned objects. In other words, throw away the the senderIds array and replace it with:

@property(nonatomic, strong) NSArray *messages;

Your find block becomes trivial:

[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    self.messages = objects;
}];

Answer messages.count for numberOfRowsInSection, and then pick what you need from the objects in cellForRowAtIndexPath...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

    PFObject *message = self.messages[indexPath.row];

    NSString *messageBody = [message objectForKey:@"messageBody"];
    PFUser *sender = [message objectForKey:@"sender"];
    NSString *senderName = [sender username];
    cell.textLabel.text = senderName;

    return cell;
}