endTurnWithNextParticipants doesn’t trigger receivedTurnEventForMatch after update to iOS 8.3 and swift 1.2

i0S Swift Issue

Question or problem in the Swift programming language:

Has anyone noticed any changes to turn based match notifications since updating to iOS8.3? In my app when I call endTurnWithNextParticipants, prior to the upgrade, this was resulting in a notification being sent to the opponent which would trigger a receivedTurnEventForMatch being invoked on their device but this no longer is the case. When the opponent terminates the app and restarts it they can see it is their turn so the match in game centre was correctly updated with the order of participation but this doesn’t seem to take effect dynamically any more.

Anybody else see this? I’m hoping it’s just a temporary glitch in game centre sandbox environment.

I have raised a bug report with apple to see if it actually is a bug or if there is some undocumented behavioural changes in iOS8.3 we need to know about.

How to solve the problem:

Solution 1:

Update:
Apple has responded to the bug report:


Please verify this issue with iOS 8.4 beta 4 (Build: 12H4125a) and
update your bug report at http://bugreport.apple.com/ with your
results.
iOS 8.4 beta 4 (Build: 12H4125a)
https://developer.apple.com/ios/download/ Posted: June 9th, 2015

Unfortunately I am not able to install iOS 8.4 beta 4 on my devices and can’t tell you whether it’s fixed now. If anybody of you has this opportunity, please share.


I have submitted a bug report to Apple regarding this issue and will post updates here when they have responded.

My turn-based game has this workaround. Looks terrible, but it’s working again – maybe it helps you.

- (void)sendGameStateWith:(NSMutableDictionary *)data
{
    if (self.match) {
        NSData *matchdata = [NSKeyedArchiver archivedDataWithRootObject:data];

        GKTurnBasedParticipant *opponent = [self.match.participants objectAtIndex:0];
        GKTurnBasedParticipant *localPlayer = [self.match.participants objectAtIndex:1];
        if ([self.localPlayer.playerID isEqualToString:opponent.playerID]) {
            opponent = [self.match.participants objectAtIndex:1];
            localPlayer = [self.match.participants objectAtIndex:0];
        }

        // HINT: Remove this workaround when Apple has fixed it. 
        [self.match saveCurrentTurnWithMatchData:matchdata completionHandler:^(NSError *error) {
            if (error) {
                NSLog(@"Error on saveCurrentTurnWithMatchData");
                [self sendGameStateWith:data];

            } else {
                [self.match endTurnWithNextParticipants:[NSArray arrayWithObjects:opponent, localPlayer, nil] turnTimeout:turnTimeout matchData:matchdata completionHandler:^(NSError *error) {
                    if (error) {
                        NSLog(@"Error: Send Game Center state");
                    } else {
                        NSLog(@"Sent Game Center state");
                    }
                }];
            }
        }];
    }
}

Solution 2:

I have the same problem.. I have multiple devices, and device with 8.3 do not trigger receivedTurnEventForMatch event, but when is application in background then banner notification about turn will appear as usual. Problem is only in case when application is in foreground.

On the other hand, devices with 8.2 or 8.1.3 works well in any case. So this is certainly caused by 8.3. Also device with 8.3 is in Xcode marked as ineligible. Does it happen to you too?

Solution 3:

Updated answer:

My original idea, below, turns out to not be reliable. You cannot count on the saveCurrentTurnWithMatchData to send timely notifications. It works most of the time, but at least 10% of the time, it fails to send a notifications as well. Of all the ideas posted here, the only thing I have found that works 100% reliably is to set up a timer loop on the non-active machines and continuously watch until you become active.

-(void)isMatchActive:(NSTimer *)timer
{
    NSString *matchID = (NSString *)timer.userInfo;

    [GKTurnBasedMatch loadMatchWithID:matchID withCompletionHandler:^(GKTurnBasedMatch *match, NSError *error)
    {
        GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
        GKTurnBasedParticipant *currentParticipant = match.currentParticipant;

        if ([localPlayer.playerID isEqualToString:currentParticipant.player.playerID])
        {
            //we have become active. Call the event handler like it's supposed to be called
            [self player:localPlayer receivedTurnEventForMatch:match didBecomeActive:false];
        }
        else
        {
            //we are still waiting to become active. Check back soon
            float dTime = 5.0;
            gameController.IOS8BugTimer = [NSTimer scheduledTimerWithTimeInterval:dTime
                                                                          target:self
                                                                     selector:@selector(isMatchActive:)
                                                                     userInfo:matchID
                                                                      repeats:NO];
         }
     }];
}

Original Answer:

So, I have a work around that is kludgy but looks promising. Note that, per my comment above, the subsequent players still receive events after the current player executes saveCurrentTurnWithMatchData. So I use that send my own custom signal:

  1. I added a string to my matchData for the “next player’s ID.”

  2. Right before I call endTurnWithNextParticipants, I set that string to the ID of the next player in the rotation.

  3. I call saveCurrentTurnWithMatchData

  4. I moved the call to endTurnWithNextParticipants inside saveCurrentTurnWithMatchData‘s completion handler, to ensure it doesn’t fire until after saveCurrentTurnWithMatchData.

  5. For the purposes of this work around, I added the GKTurnBasedEventHandlerDelegate to my code. handleTurnEventForMatch is broken in 8.3 too, but I consolidated all of my workaround code there and didn’t have to make changes to receivedTurnEventForMatch

  6. In handleTurnEventForMatch, I check to see if the “next player” string is me. If so, I assume that the current player just saved the game in step 2, signalling his intention to pass the turn to me.

  7. I start a timer loop, reloading the match data until it shows that I have become the active player.

  8. When I see that I am now the active player, I reset my custom next player string to nil and I manually call receivedTurnEventForMatch passing it the match data I just downloaded.

To summarize, player1 sends an extra saveTurn event to signal that he intends to end the turn. When player2 receives that signal, he re-reads the match data until it shows that he has become active. Then he calls his own receivedTurnEventForMatch with the updated match data, allowing the turn to proceed.

I haven’t gotten through all my scenarios yet, but it looks like this is going to work.

Solution 4:

We had the same issue under iOS 8.3 and it has been fixed in yesterdays release of iOS 8.4.

UPDATE 1

in our case the workaround of @appsunited did solve it for iOS 8.3 and is not necessary anymore with iOS 8.4.

we tested both sandbox and our current app store version. and played with an iPad 3 with iOS 7.1.2 against another iPad 3 with iOS 8.3 and yesterday 8.4.

Hope this helps!