Question

(Note: You can probably even answer this without reading any of my code, if you don't feel like reading this novel of a question, I've explained at the bottom)

I have some code that's throwing me a bit off course. It seems like my code is calling [tableView reloadData] before the data is ready, although I set the data before I call reloadData. This is probably a newbie-question, but I have never dealt with this kind of code before, and I'd be very happy if someone could explain to me exactly what is going on, and why some of my code is allowed to jump out of the chronological logic..

My question has nothing to do with GameCenter, I think this is a general Objective-C thing, but I am experiencing it with GameCenter, so even if you don't know anything about GameCenter, you'll probably be able to understand what I'm talking about.

I have a tableViewController, and I am filling the tableView with matches(games) from GameCenter, and showing the names of the opponent(always one opponent in my turn-based-game).

I have put this in my viewDidAppear:

[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error) {       
//Gets an array (*matches) of GKTurnBasedMatches(All matches the local player is involved in)
    if(error)
    {
        NSLog(@"Load matches error: %@", error.description);
        return;
    }
    //putting them in an array
    matchList = [[NSArray alloc] initWithArray:matches];      

    //So far only for testing purposes: I am creating an array for player names:
    playerNames = [[NSMutableArray alloc] init];
    for(GKTurnBasedMatch* mat in matchList)
    {//For every match in the array of matches, find the opponent:
        GKTurnBasedParticipant *otherParticipant; //These games only have 2 players. Always.
        for(GKTurnBasedParticipant *part in [mat participants])
        { //Loop through both participants in each match
            if(NO == [part.playerID isEqualToString:[GKLocalPlayer localPlayer].playerID])
            {//If the participant *part is NOT the local player, then he must be the opponent:
                otherParticipant = part; //Save the opponent.
            }   
        }

        if(otherParticipant != nil && otherParticipant.playerID != nil)
        { //If the opponent exists(like he does)
            [GKPlayer loadPlayersForIdentifiers:@[otherParticipant.playerID] withCompletionHandler:^(NSArray *players, NSError *error) {
               //Returns an array with only the opponent in it
                if([(GKPlayer*)[players objectAtIndex:0] displayName]!=nil)
                {//If he has a name:
                    NSString *nam = [[NSString alloc] initWithString:[(GKPlayer*)[players objectAtIndex:0] displayName]];
                    //Add the name to the name-array
                    [playerNames addObject:nam];
                }
                else
                {//If he doesn't have a name, put "No name" into the name array(is not happening)
                    NSString *nam = [[NSString alloc]initWithString:@"No name"];
                    [playerNames addObject:nam];
                }     
            }];
        }
        else
        {//If for some reason the entire opponent is nil or he doesn't have a playerID, which isn't happening, add "No name";(This is not happening)
            [playerNames addObject:[NSString stringWithFormat:@"No name"]];
        } 
    }
    //And THEN reload the data, now with the NSMutableArray 'playerNames' ready and filled with opponent's names.
    [[self tableView] reloadData];
}];

This is my CellForRowAtIndexPath:

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

    NSString *name = @"Nobody";
    if(playerNames!=nil && [playerNames count]>indexPath.row)
        name = [playerNames objectAtIndex:indexPath.row];
    [[cell textLabel] setText:[NSString stringWithFormat:@"Game vs. %@", name]];

    return cell;
}

The thing is: when the view controller appears: as expected, the tableView is initially empty, but after about half a second, it fills with "Game vs. Nobody" in the correct number of games. If I start two games, and re-enter the view controller, then it's blank at first, and then show "Game vs. Nobody" on two cells. BUT: if I scroll the rows outside the screen, and force them to call for cellForRowAtIndexPath again, then the correct name shows up. So it seems like the [tableView reloadData] in viewDidAppear is called before I ask it to, before the array with names has been filled.

I assume this has something to do with the whole CompletionHandler-thing, as I have never used this before, but I thought everything inside the CompletionHandler was supposed to happen chronologically? Is there something I can call, like onCompletion{} or something?

Ideally, I want an UIAlertView or something to show up with a spinning wheel, the UIActivityIndicationView, and I want it to show until all the data has been loaded correctly. I don't know how to find out WHEN the completionHandler is done, and what it really is..

Was it helpful?

Solution

The playerNames array is being filled within the completion handler for loadPlayersForIdentifiers, which is (I assume) asynchronous. The names array is not populated until the completion handler runs, but you are calling [tableView reloadData] within the other block.

To fix, just move the reload call within the completion block for loadPlayersForIdentifiers (at the end)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top