After Invite is clicked then how to launch view controller from didAccept?

I am using two real iPhones (A and B) to test game center "Invite" logic and I am getting confused.


I use iPhone-A to start my game and then launch the GKMatchmakerViewController. I then select the "Invite Friends" button and select a friend to invite (which is iPhone-B).


On iPhone-B, I receive a popup message asking whether I want to join the game. After I click the popup message, I see my game is launched on iPhone-B. At this point, I assume the player( player: didAccept invite:) function would be called on iPhone-B automatically and then a GKMatchmakerViewController should be created by providing the GKInvite object by calling the function below:


let mmvc = GKMatchmakerViewController(invite: inviteToAccept)

mmvc!.matchmakerDelegate = self

viewController.present(mmvc!, animated: true, completion: nil)


Where does the "viewController" comes from to allow me to present/display the GKMatchmakerViewController?


Can someone explain how they present the GKMatchmakerViewController when the didAccept function is called?

Accepted Reply

In Objective C:



- (void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite{
    GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:invite];
    mmvc.matchmakerDelegate = self;
    [[self topController] presentViewController:mmvc animated:YES completion:^{
            mmvc.view.frame=CGRectMake(0,mmvc.view.safeAreaInsets.top, mmvc.view.bounds.size.width, mmvc.view.bounds.size.height-mmvc.view.safeAreaInsets.top);
    }];
  }




-(UIViewController *)topController{
    
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (true){
        if (topController.presentedViewController) {
            topController = topController.presentedViewController;
        }else if([topController isKindOfClass:[UINavigationController class]]) {
            UINavigationController *nav = (UINavigationController *)topController;
            topController = nav.topViewController;
        }else if([topController isKindOfClass:[UITabBarController class]]) {
            UITabBarController *tab = (UITabBarController *)topController;
            topController = tab.selectedViewController;
        }else{
            break;
        }
    }
    return topController;
}

Replies

In Objective C:



- (void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite{
    GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:invite];
    mmvc.matchmakerDelegate = self;
    [[self topController] presentViewController:mmvc animated:YES completion:^{
            mmvc.view.frame=CGRectMake(0,mmvc.view.safeAreaInsets.top, mmvc.view.bounds.size.width, mmvc.view.bounds.size.height-mmvc.view.safeAreaInsets.top);
    }];
  }




-(UIViewController *)topController{
    
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (true){
        if (topController.presentedViewController) {
            topController = topController.presentedViewController;
        }else if([topController isKindOfClass:[UINavigationController class]]) {
            UINavigationController *nav = (UINavigationController *)topController;
            topController = nav.topViewController;
        }else if([topController isKindOfClass:[UITabBarController class]]) {
            UITabBarController *tab = (UITabBarController *)topController;
            topController = tab.selectedViewController;
        }else{
            break;
        }
    }
    return topController;
}

Thanks for the code logic.


When a player receives an Invite, then the application on the iPhone will automatically open up and start. I assume the "didAccept match" function would be called at this time.


1) Since the application starts up automatically, then the view controller (which is active at the time the "didAccept match" function call comes in) should always be the main view controller of the application. Do you agree? If so, then why do we need code logic to check for specific view controllers (ie: navigation, tabBar, etc.)? Perhaps when my application starts up then I can set a "currentViewController" variable inside the GameKitHelper class to be the correct view controller to use when the "didAccept match" function is eventually called.


2) Do I have to worry about how quickly the "didAccept match" function is called automatically? Could it be called before my application has time to start up completely, thus I miss the "didAccept match" function call altogether?

>>1) Since the application starts up automatically, then the view controller (which is active at the time the "didAccept match" function call comes in) should always be the main view controller of the application. Do you agree?


I guess this is not true since I could already be inside my application and be located in some submenu within my application when the Invite comes in. This way I would definitely not be located in the main view controller of my application, I would be in some submenu view controller instead.

1) Yes, your app could already be opened in foreground or background.

And if your app is launched I'm not sure this is correct - "the view controller (which is active at the time the "didAccept match" function call comes in) should always be the main view controller of the application"


2) If your app is not open, it will open your app and call this method after applicationDidFinishLaunchingWithOptions:

>> And if your app is launched I'm not sure this is correct ....

Yeah, I realized this myself as well. Please see my previous reply in the thread :>)


>>Yes, your app could already be opened in foreground or background.

I am making a game where all other players are waiting for each other to take turns and do stuff. If a player's application enters the background then I do not think the application can receive any messages from Game Center anymore. If this is true, then the player is missing vital information being sent from other players and is no longer in sync with other players either. In addition, all of the other players are stuck waiting for the player (who is now in the background) to do sometehing.



1) How do you handle your game for the player when the application enters the background? Do you kill the game for the player when they are about to enter the background?


2) While in the background, does my application actually run at all (ie: will my timers still expire, can my code do anything, etc.)?

My comment "(a)nd if your app is launched I'm not sure this is correct" referred to a launch of your app. That differs from your statement "I guess this is not true since I could already be inside my application and be located in some submenu". I am saying your limiting statement "since I could already be inside..." is not necessary.



> waiting for the player (who is now in the background) to do sometehing


That player will tap on the invite notification and that will reopen the app.


> backgrond - Once the game is underway, if a player enters the background they send out a disconnect notice through:

- (void)match:(GKMatch *)match player:(GKPlayer *)player didChangeConnectionState:(GKPlayerConnectionState)state{

Your app will shut down when it goes into background.



I may have mentioned this before - get a friend and play Go using "Go Game Connect". Experiment with the various notifications that come in and how that app handles them.

Thanks for the information. I do have my invite processing working but have one more issue to resolve to get the behavior how I want it.


In the didAcceptInvite function, if I determine the player is currently involved in match processing (ie: currently setting up a game or actually playing a game) then I do not want to accept their invite. I do not know how to send a cancel/reject for the invite so I plan on just ignoring it.


Is there a better way to handle this?



The problem I have is that when the little Invite popup banner is presented by Game Center (ie: Do you want to play?), there is no clear "YES-NO" option associated with the popup banner. Like me, I click on the banner to read more about what the banner is asking me. However, it seems I am actually accepting the invite by simply clicking on the popup banner itself. I guess this is what eventually results in the didAcceptInvite function call. At this point, since I clicked on the popup banner and accepted the match, then it seems like Game Center now considers my LocalPlayer to be associated with the new match related to the invite I just unknowingly accepted, instead of having my LocalPlayer remain associated with the game I am currently playing.


As a test, when iPhone-A has the GKMatchmakerViewController currently spinning looking for random players and then iPhone-A receives a popup banner Invite request, then if iPhone-A clicks the popup banner then you will see the current GKMatchmakerViewController window stops hunting for players and clicking the "Cancel" link in the top left corner does nothing. As a result, I imagine if I was currently involved in an active game and an invite popup banner comes in, then if I click on the banner then my active game would no longer work either.


The problem I see is that clicking on that little popup banner seems to mean you accepted the invite and after you do this you cannot indicate you want to cancel that acceptance.


How do you handle a didAcceptInvite function being called when yo uare in the middle of an active game?

I assume that if the player taps on the notification (rather than swipes it up) they want to accept the invite. I have a different issue, I am concerned that in a two player game in which there is a disconnect both players could try to invite the other player. So I may have the mmvc viewController showing that needs to be dismissed. Here's what I do:


- (void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite{
    if(!didAcceptInviteVCShowing){
     //   NSLog(@"didAcceptInviteVCShowing");
        [self acceptThisInvite:invite];
    }else{
        [[self topController] dismissViewControllerAnimated:YES completion:^{
          //  NSLog(@"we may have problems here");
            [self acceptThisInvite:invite];
        }];
    }
}


-(void)acceptThisInvite:(GKInvite *)invite{
    didAcceptInviteVCShowing=YES;
    GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:invite];
    mmvc.matchmakerDelegate = self;
    [[self topController] presentViewController:mmvc animated:YES completion:^{
        mmvc.view.frame=CGRectMake(0,mmvc.view.safeAreaInsets.top, mmvc.view.bounds.size.width, mmvc.view.bounds.size.height-mmvc.view.safeAreaInsets.top);
    }];
}
//didAcceptInviteVCShowing is set and unset in various places throughout the program to indicate if teh VC is showing

>>I assume that if the player taps on the notification (rather than swipes it up) they want to accept the invite.

Yeah I thought about this as well and decided to re-write my code logic. Now, if the player taps on the invite notification (ie: accepts the invite) then I will remove the player from the current game being played (if existing) and then display the didAccept invite view controller to the player.


>>I have a different issue, I am concerned that in a two player game in which there is a disconnect both players could try to invite the other player

If both players try to invite each other at the same time, then each other's application will have their didAccept function called. Both players will present the didAccept invite view controller to themselves. At this point, I will let Game Center figure out what to do. At worse, nothing really happens and the presented view controller just keeps spinning and spinning. The two players have to know they invited each other so they will most likely press the "cancel" link in the top-left of the didAcceptInvite view controller to exit. They will then text each other and determine who will send the invite instead. As a result, I am not too worried about this happening. Anyway, I do not think there is anything we can do about it anyway.

This is to cover the first 'collision' which is possible; actually probable. After tapping the invitation notification, in didAcceptInvite you want to show that vc so the user can, actually, accept the invite. But if the user is themself in the process of inviting someone else (i.e. a first collision) they already have that vc showing. They need to cancel that vc (and ignore any RSVP to their invitation) before accepting the invitation of the other user. Usually one or the other user will win this and resolve the first collision smoothly.


But, as you suggest, if both users accept the notification resulting in a second collision then both invitations will fail and a do over will be required.

My code handles the case you indicated.


My code checks for two things when the didAcceptInvite comes in:


1) Is an active game in progress?

If (1) is TRUE, then my game sends a notification to my gamePlay module indicating the player wants to quit the game. This will result in the actual game cleaning itself up and also ensuring all Game Center match data is cleaned up properly. After the current match processing is cleaned up, then I present the Invite View Controller so the player can join the game they are being invited into.


2) Is a match in the process of being created (ie: although gameplay has not started)?

This represents the time in between either one of the following:


a) When a player starts setting up a match by presenting the GKMatchmakerViewController (ie: by calling findMatchWithGKMatchmaker() function) and the eventual call to the "delegate.matchStarted()" indicating gameplay should start


b) When a player tries to find match to join (ie: not hosting the match themselves) by calling "GKMatchmaker.shared().findMatch(for: matchRequest, withCompletionHandler: " function and the eventual call to the "delegate.matchStarted()" indicating gameplay should start.


If (2) is TRUE then my code ensures all Game Center match data is cleaned up properly. After the current match processing is cleaned up, then I present the Invite View Controller so the player can join the game they are being invited into.



** A third type collision can occur, although quite rare, which I am not going to handle. This is when the player accepts an invite from another player and then receives another invite, which they also accept, while the first invite is still being processed. In this case, we must cancel the first match when the first invte "didFind match" is called. In my opinion, this would not be worth the effort to track such an occurence as this would be an incredibly rare scenario.