Game Kit stores a strong reference to the completion handler that I send to authenticateWithCompletionHandler:, which means that each time the user exits and enters the app, it gets called again. This makes sense, but it causes a problem with a use case I have:
1) I prompt the user to log into Game Center when the app launches.
2) They tap Cancel because they want to play single player for a while. Therefore they are not logged in.
3) At some point, they decide they want to play online, so they tap my "Play Online" button.
4) This ought to show a screen where they can set up online game options, etc, but I notice they have no authenticated player, so…
5) I prompt the user again to log in to Game Center.
6) The user logs in this time, and in the completion handler I show my online game options screen.
Step 6 is the where the problem lies: every time the user leaves and re-enters the app, it will show my game options screen, because my completion handler is repeated. If I take out the code in the completion handler to show the online game options, the user has to tap the button twice - once to login, and once again to show the online options.
What is the smart solution to this?
For reference, a simplified version of my code looks like this:
- (IBAction)playOnlineTapped:(id)sender
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
[localPlayer authenticateWithCompletionHandler:^(NSError *error) {
if (localPlayer.authenticated) {
[self showOnlineGameOptions];
}
}];
}
So many of the examples I have read assume that the user logs in first time, but I don't think that's always going to be the case.
Thanks in advance for your help!
One solution to this would be to not have the completion handler change your views. This seems like an odd idea because the completion handler is called asynchronously and could happen any time after you request authentication.
Instead, the completion handler could check if you are in the online menu and enable buttons for you. Until then, have these buttons disabled and show a message saying "waiting for Game Center". The key is not to trigger any scene transitions in your completion handler. That would be bad design because you don't know when this block is called.
Another hint. If the user declines to log in to Game Center, your authentication request at some point won't prompt the user. If I remember right, you will receive GKErrorUserDenied immediately. Therefore, you should tell users that they can launch your game from within the Game Center app.
Related
I am making a multiplayer feature to a game I made.
Everything is working, except when I am in matchmaking and both players are connected, if one person hits the "Cancel" button the other device gets no notice of the canceling.
On the other device it the words change to say "Disconnected" however none of the delegate methods are called.
How can I handle this?
You should implement the GKMatchmakerViewControllerDelegate protocol.
Unfortunately, there is not a method (that I know of, or could find with almost 3 months of looking into it) that is called when one person disconnects in matchmaking after they have begun to connect.
Therefore, the way I got around this is once the GameViewController is presented it waits one second and then calls a method to check to see if it is connected to someone else.
To do this I have it so once the game begins each player sends the other player a random number (used to determine non-related settings later on - such as who gets to go first). When it calls the method to check to see if it is connected it just checks to see if the random number has been assigned. If so, then it begins the game as normal, if not, it ends the game and pops back to the menu.
My app uses Game Center. I try to log the user when the VC loads.
If he was previously logged - great, it logs him in with the notification on top. If he wasnt logged in - great, it shows the Game Center login VC.
However, i want not to 'jump' on the user with the Game Center login VC when he enters the app. I want to have a button that brings up the Game Center login VC, which is easy to do, so only when he clicks the button, the Game Center login VC will come up.
The problem is - i discovered (through trial and error), that if i try to log the user in, and it fails (for example, the user was not logged in to Game Center at all), and if i dont bring the Game Center login VC at that moment - within the first call of the handler - i cant bring it up later on.
I'll explain - I implemented the button i talked about, which brings the GC login VC up. if i dont try to automatically log the user in on load, the button works as expected. But if i do, and it fails (for whatever reason), the button will not bring the VC up no matter what.
observations -
this shows that this is indeed Apple policy.
trying to bring the GC login VC up after failed handler login doesnt work at all, not related at all to VC appearance.
I found this question here on stackoverflow, but could not find my answer there.
My questions to you are:
Is that intended that you can only call the handler once per app run (Even though VC was not shown at first handler call)?
Is it possible to do what i asked? if so - how? I would love to able to try to login without showing VC only if i know it will succeed, so i can 'save' the handler call for the button that shows the GC login VC. I know that the information is available in the handler (according to this), im wondering if there is another way.
To conclude my questions - Do i have to 'jump' the user with the
login VC the moment i try to log him in? (if there is a GC logged in
user on the device, a VC is not required)
I hope this was clear, since its a confusing situation.
Sorry for the long post!
Thanks alot for your time!
You keep some NSUserDefaults like 'userHasAttemptedToUseGameCenter'. It starts as NO or undefined, which to you means NO. Then when they press the game center button you set to YES and try to do the game center authentication. From then on every time they open the game (or at least every time they go to a game center related screen / feature, then you do game center authentication.
Even when its working fine because you have a game center user it can be a pain because the 'Welcome back' game center banner will pop down and cover the top part of your games UI for the first few moments.
If you just let the game center authentication come up every time but the user doesn't want it, I think after 3 failed attempts to authenticate iOS will NEVER show the authentication again. Your user will then be totally stuck if they change their mind later and want to use a feature that needs game center. You can detect that case only because game center won't authenticate! And all you can do then is tell the user to go to the Game Center app and log in there. Its hell to test. If one of your test devices gets into this locked out state you have to do a 'Reset all content and settings'.
Please someone chime in if this has gotten any better in iOS8.
My app attempts to log the player in when it finishes loading, like good little apps should (so says Apple). But if the player chooses to cancel the initial log-in, I wanted the app to re-attempt to authenticate the player if the player taps the leaderboard button in the game. (otherwise, of course, the button couldn't do anything if the player is not authenticated)
Unfortunately, after some research I discovered that Apple does not seem to allow an app try again to re-authenticate the player after the player cancels the first time, until the player exits and re-enters the game. (If the player cancels three or so times, Apple goes aggro and disables Game Center from the app on that device entirely. Even logging into Game Center from the stand alone app won't help after that point.)
Is there any way around this so that my app can attempt the authentication both when the app loads, and any time the player taps leaderboard button while not logged in? Or do I just have to have my leaderboard button display a message when not authenticated saying that Apple's being dumb and overprotective and not letting my app respond the way it should? (Perhaps not quite in those words...)
I've used an alternative approach that might solve your requirement. When they tap on the leaderboard, check the Game Center connection state.
If it is not GCPConnectionStateEnabledFully then throw up an alert like this "Error connecting to Game Center. Please make sure you are signed into Game Center and check your internet connection". Then inevitably they will leave your app to check and when they return the Login prompt will appear a few seconds after they return to the app.
Testing note: When you are testing these scenarios you might cancel the login prompt 3 times. If you do that it will stop prompting you altogether and you need to reset all settings in the Settings app. I remember this being really frustrating.
I have a turn based game and am trying to end the game when someone forfeits from Game Center out of turn. I can't figure out what is called when the player out of turn actually presses the "forfeit" button. I want to implement,
participantQuitOutOfTurnWithOutcome:withCompletionHandler:
but don't know where to put it and call it. I have tried to put it here:
-(void)handleTurnEventForMatch:(GKTurnBasedMatch *)match
and
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController playerQuitForMatch:(GKTurnBasedMatch *)match
but neither seem to be called when a player presses the "forfeit" button out of turn. What am I missing?
You'll want to call participantQuitOutOfTurnWithOutcome:withCompletionHandler: whenever you determine the user wants to forfeit—so in your case, when they tap the forfeit button (and possibly after hitting 'Yes' on a confirmation).
In that case, you'll want something like this:
-(void)playerChoseToForfeit {
[self.match participantQuitOutOfTurnWithOutcome:GKTurnBasedMatchOutcomeQuit withCompletionHandler:^(NSError *error) {
//tell the user that they've forfeited (or not, if there's an error!)
}];
}
Basically, it'll be in your own method—not (necessarily) a delegate one.
Five years after you asked this question and Apple still hasn't updated the GKLocalPlayerListener protocol to handle this situation. I am also trying to rely solely on the Game Center MatchMaker view controller (GC MMVC) and I ran into the same problem you did, where if the user forfeits through the GC MMVC when it isn't their turn, then the match is left in a conflicted state: "completed" for the quitter, and "active" for the remaining players.
I found out by checking the matchOutcome of the opponent who quit out of turn after calling match.loadMatchData, that the opponent's outcome was set to .quit.
The reason that Game Center set the quitter's status to .quit but left the game active and set the currentParticipant to the next player was for games with more than two players. Since my game is a two-player only game, I didn't understand why player(wantsToQuitMatch:) wasn't being called.
So every time you call loadMatchData, you should check the matchOutcome of all other players and take appropriate action immediately. In my case, I set the quitter's outcome to .lost, the localParticipant's outcome to .won, and then call match.endMatchInTurn with the updated match data. Then I will display the loaded match to the localParticipant with a message saying the other player quit.
If you don't call match.endMatchInTurn then the remaining player is left hanging with an active match but no active opponents.
I have a blackberry application in which i want to show a "please wait" modal screen(which is a FullScreen push as modal screen) while sending a server request and if the user hitting the device back button , pop the modal screen and current active screen.This works fine.
My problem is that : I used a callback for server request in the active screen.But the callback executed even after popped up the screen.
Exactly whats happening while calling popScreen()? How can i remove all callbacks and refresh the screen if the user pressing back button while server request happens?
Thanks in Advance
There are several solutions to this problem of course. I guess the server request is sent asynchronously.
The simplest way I think out is having a flag and when the callback
is triggered check whether the user cancelled the action (pressing
the back button).
Another solution, perhaps not that nice is to check whether the
Loading Screen is still in the display stack.
What I think would be a proper solution is to have a stack of
cancelled http operations, so you can stop requests at any time. Then
if the request to the server has been sent, before calling the
callbacks you can check if the operation has been cancelled.
Otherwise, you just avoid sending the request to the server.
When you call popScreen, the screen which is on top of the display stack (the screen in the foreground) is removed from the stack and the Screen is refreshed (a paint event is triggered) to reflect the changes. Make sure you execute Screen.pop() on the UiThread:
UiApplication.getUiApplication().invokeLater(runnable)
In your scenario, how is the callback handled? Is it a delegate you passed along with the request or is a listener you register?
When I refer to delegate I mean something like the following:
Server.sendRequest(request, objectWithCallbacks)
and callbacks of the objectWithCallbacks (and only objectWithCallbacks) will be called accordingly. On the other hand, a listener would be something like:
Server.addListener(objectWithCallbacks, request)
Server.sendRequest(request);
this way, all the objects listening to "eventName" will get their callbacks triggered accordingly.
As far as I see, your callback will be always executed but in the callback itself you can check if the screen is currently displayed.
if( this.isDisplayed() ) {
// Do the magic
}else{
// Do nothing
}
Good Luck