I have seen in posts around stack overflow that shows snippets of handling GameCenter authentication. However, none of these solutions address any of the problems that real world use cases cover. Namely, the [GKLocalPlayer localPlayer].authenticateHandler is just a call back of the status, and not much else. It provides a view controller, but there are massive inconsistencies in .authenticated, and error states.
There are a few things I am trying to do: 1. Not pop up the game center login until a feature uses it 2. Try to authenticate silently on app launch 3. Provide some info to the user why GameCenter features are not working 4. Provide a recovery mechanism
Namely if there is an error reported how can I show the login dialog anyways?
I get this error with no viewController:
Case 1:
Error in GameCenterManager::authenticateLocalPlayer [The Internet connection appears to be offline.]
Despite its error message, the device is completely online, as safari loads cnn.com just fine.
Case 2:
Someone closes the login screen because they are not ready, in which case .authenticated comes back as true, viewController remains at nil, yet all game center calls will fail. Why is the [GKLocalPlayer localPlayer].authenticated set to true when it isn't?
Case 3:
Error in GameCenterManager::authenticateLocalPlayer [The operation couldn’t be completed. (NSURLErrorDomain error -1009.)]
This keeps occurring yet there is nothing the app can do for the user. In this case what should the messaging be? Switch apps to Game Center and login there?
Case 4:
Error in GameCenterManager::authenticateLocalPlayer [The requested operation has been canceled or disabled by the user.]
This happens if the user cancels the viewController the app was told to present by apple. Yet, there is also no recovery or detecting this state.
Case 5:
Error in GameCenterManager::createMatch [The requested operation could not be completed because local player has not been authenticated.]
This happens if the user was logged in, but for whatever reason logs out of GameCenter then returns to the app. The app will be told the user is still authenticated when it is clearly not, yet there are no calls I can make to bring up another login.
So essentially, if GameCenter doesn't just silently work, what are we to do as app designers? Alert view and tell them to go login using the game center app and restart the app?
Here is my authentication code:
//******************************************************
// Authenticate
//******************************************************
-(void)authenticateLocalPlayer:(bool)showLogin
{
if( showLogin && self.loginScreen != nil )
{ [[WordlingsViewController instance] presentViewController:self.loginScreen animated:YES completion:nil]; }
if( [GKLocalPlayer localPlayer].isAuthenticated )
{
NSDLog(NSDLOG_GAME_CENTER,@"GameCenterManager::authenticateLocalPlayer LocalPlayer authenticated");
}
__weak GameCenterManager* weakSelf = self;
[GKLocalPlayer localPlayer].authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
if (error != nil)
{
NSDLog(NSDLOG_GAME_CENTER,@"Error in GameCenterManager::authenticateLocalPlayer [%@]", [error localizedDescription]);
}
else
{
if (viewController != nil)
{
NSDLog(NSDLOG_GAME_CENTER,@"GameCenter: No authentication error, but we need to login");
weakSelf.loginScreen = viewController;
}
else
{
if ( [GKLocalPlayer localPlayer].authenticated )
{
NSDLog(NSDLOG_GAME_CENTER,@"GameCenter localPlayer authenticated");
weakSelf.gameCenterAvailable = YES;
weakSelf.localPlayer = [GKLocalPlayer localPlayer];
[weakSelf retrieveFriends];
[weakSelf loadPlayerPhoto:weakSelf.localPlayer];
for ( id<GameCenterDelegate> listener in weakSelf.listeners )
{ [listener onPlayerAuthenticated]; }
}
else
{
weakSelf.gameCenterAvailable = NO;
}
}
}
};
}
This function is called twice: once at app startup to hopefully create a valid login state, and 2nd if the user is not authenticated and they try to use an app feature that requires game center. In this app, it is creating a turn based match or viewing friends
You're encountering many of the same complaints I have about the Game Center API. I've been trying to achieve the same 4 things you are. The TL;DR version: Game Center simply doesn't support it. >< But there are some things you can do to reduce the pain.
One general thing that helped me: make sure to check both the
NSError
as well as it's.underlyingError
property. I've seen several cases where theNSError
is too vague to be helpful, but the underlying error has more specific details.Case 1: Can you share the error domain and error code for the both the NSError and the underlyingError?
Case 2: I have an open bug with Apple on this, for a looooong time. There are several cases, including being in Airplane mode, where the authentication fails but
.authenticated
returns true. When I wrote a bug on this, Apple closed it saying this was "by design" so players could continue to play the game using any previously cached data. So, I appended the bug with several scenarios where cached data causes significant problems. My bug was re-opened and has sat there ever since. The design philosophy seems to be: "well, just keep going and maybe it will all work out in the end." But it doesn't work out in the end, the user gets stuck, unable to play and they blame my game, not Apple.The only mitigation I have found is exactly what you're already doing: Always always always check the
NSError
first in the authentication handler. The view controller and.authenticated
are totally unreliable if an error has been set.If there is an error, I pass it to one dedicated error handler that displays alerts to users and tells them what they need to do to recover.
Case 3: I have hit -1009 as well. From what I can discern it happens when I have network connection, but Game Center never replied. That could come from any disruption anywhere between my router up-to-and-including Game Center servers not responding. I used to see this a lot when using the GC Test Servers. Not so much now that the test servers were merged into the prod environment.
Case 4: You are 100% correct. there is no in-game recovery. If the user cancels the authentication, that's the end of the line. The only way to recover is to kill the game (not just leave and re-enter) and restart it. Then, and only then, you can present another login view controller.
There are some things you can do to mitigate this, though. It will directly break your #1 goal of delaying login until needed, but I haven't found anything better:
NSError
of domain =GKErrorDomain
and code =GKErrorCanceled
. WHen I see that combo, I put up a warning to the user that they cannot play network games until they've successfully logged in and to login they will have to stop and restart the game.It sucks, but at least the user isn't stuck.
Case 5: This is one of the examples I cited in my bug referred to in case 2. By letting the user think they're logged in when they really aren't, they try to do things they really can't do, and eventually something bad will happen.
The best mitigation I have found for this is the same as Case 4: don't let the user start a session until you see the authentication handler fire with no errors AND you can successfully download a sample leaderboard to prove the network connection.
In fact, doing a search through all of my code bases, I never use .authenticated for any decisions anymore.
Having said all of that, here's my authentication handler. I won't say it's pretty, but thus far, users don't get stuck in unrecoverable situations. I guess it's a case of desperate times (working with a crap API) requires desperate measures (kludgy work arounds).