What is the proper way to handle invitations from other players in iOS 7?
After my view did load on my root View Controller, I'm calling a game center authentication method, after that I'm setup a invitation handler like so:
[[GKLocalPlayer localPlayer] registerListener:self];
My view controller adopt GKLocalPlayerListener and GKInviteEventListener protocols, by the way, what is the best place to register a listener AppDelegate? Maybe or maybe my custom Game Center singleton?
I add a method described in GKInviteEventListener
-(void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite
{
NSLog(#"Invite accepted!");
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:invite];
mmvc.matchmakerDelegate = self;
[self presentViewController:mmvc animated:YES completion:nil];
}
But, game center matchmaker class have such topic: Receiving Invitations From Other Players and method – matchForInvite:completionHandler:
I don't understand how to use it.
So, what I must use and how?
I think you're doing things correctly. I did things the same way, and it works for me. -matchForInvite:completionHandler is deprecated in iOS 7, so I don't do anything with it.
First I set the authentication handler. This is called when my app loads for the first time, you can set it anywhere you like, but it is best to set the handler as soon as possible.
-(void)authenticateLocalPlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
__weak GKLocalPlayer *blockLocalPlayer = localPlayer;
//Block is called each time GameKit automatically authenticates
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
[self setLastError:error];
if (viewController)
{
self.authenticationViewController = viewController;
[self disableGameCenter];
}
else if (blockLocalPlayer.isAuthenticated)
{
[self authenticatedPlayer:blockLocalPlayer];
}
else
{
[self disableGameCenter];
}
};
}
If authentication is successful, then I call this method:
-(void)authenticatedPlayer:(GKLocalPlayer*)localPlayer
{
self.isAuthenticated = YES;
[[NSNotificationCenter defaultCenter]postNotificationName:AUTHENTICATED_NOTIFICATION object:nil];
[[GKLocalPlayer localPlayer]registerListener:self];
}
Then I implemented the two listener methods:
(void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite
{
//.... insert some cleanup code here to manage view controllers etc before presenting the matchmakerviewcontroller.
[self presentMatchmakerViewControllerWithInvite:invite];
}
-(void)player:(GKPlayer *)player didRequestMatchWithPlayers:(NSArray *)playerIDsToInvite
{
//......insert some cleanup code for managing view controllers
GKMatchRequest *match = [[GKMatchRequest alloc]init];
match.playersToInvite = playerIDsToInvite;
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc]initWithMatchRequest:match];
mmvc.matchmakerDelegate = root.viewControllers[0];
[[[[[UIApplication sharedApplication]delegate]window]rootViewController]presentViewController:mmvc animated:YES completion:nil];
}
Related
I am attempting to set up multiplayer support via the following tutorial (may not be necessary to follow this link to answer this): http://www.raywenderlich.com/60980/game-center-tutorial-how-to-make-a-simple-multiplayer-game-with-sprite-kit-part-1
I have managed to get the demo for this tutorial to work, but I have not been able to get it to work with my game. I have found that the problem lies with the following block of code in 'GameSceneViewController':
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerAuthenticated) name:LocalPlayerIsAuthenticated object:nil];
}
- (void)playerAuthenticated {
[[GameKitHelper sharedGameKitHelper] findMatchWithMinPlayers:2 maxPlayers:2 viewController:self delegate:self];
}
These methods are in the second view controller, and the player is logged-in in the first view controller. In this block of code, the NSNotification center 'addObserver' code is being called in the first method, but even though the notification has been posted via the previous view controller, the 'playerAuthenticated' method is not being called.
The following code is from the GameKitHelper file which is being used by both view controllers:
- (void)authenticateLocalPlayer
{
//1
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if (localPlayer.isAuthenticated) {
[[NSNotificationCenter defaultCenter] postNotificationName:LocalPlayerIsAuthenticated object:nil];
return;
}
//2
localPlayer.authenticateHandler =
^(UIViewController *viewController, NSError *error) {
//3
[self setLastError:error];
if(viewController != nil) {
//4
[self setAuthenticationViewController:viewController];
} else if([GKLocalPlayer localPlayer].isAuthenticated) {
//5
_enableGameCenter = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:LocalPlayerIsAuthenticated object:nil];
} else {
//6
_enableGameCenter = NO;
}
};
}
Breakpoints reveal that the second but not the first Notification post is being called, so this should still work since the notification has been posted. This is all code that works well in the demo, but does not work when I have the code in my game. Both are set up for multiplayer in itunes connect, and both have the same gamecenter code.
One major difference between the demo and my game is that the demo calls the first block of code in the first view controller that appears when the game is loaded, and my game calls this instead in a viewController that is navigated to from the start view controller.
I do have a 'dealloc' method which calls 'removeObserver' in the start view controller, and from looking at the SO answer here: NSNotificationCenter selector not being called, it appears this may be removing the notification upon transfer to the other view controller, but I am not totally sure how this dealloc method would be called, and I tried commenting out 'dealloc' in the first view controller, but the same issue still occurs. If you think this I need to be retaining the notification somehow, how would this be done? If you have other ideas as to what's causing this, please help?
I have a simple iOS app which makes use of Game Center. However recently when updating it to iOS 7, I noticed that the authenticate method does not work.
One of the issues is with the code I am using to present the view controller to the user if they need to sign in to game centre, it no longer pops up the game centre login window in the app.
Another issue I have is that I can't find what method to use instead of the deprecated "authenticateWithCompletionHandler".
Here is my code:
-(void)authenticateLocalUser {
GKLocalPlayer.localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error) {
if (GKLocalPlayer.localPlayer.authenticated) {
// Already authenticated
}
else if (viewController) {
[self presentViewController:viewController animated:YES completion:nil];
}
else {
// Problem with authentication, probably bc the user doesn't use Game Center.
}
};
if ([GKLocalPlayer localPlayer].authenticated == NO) {
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {
[self callDelegateOnMainThread: #selector(processGameCenterAuth:) withArg:NULL error: error];
}];
}
}
Thanks for your time, Dan.
No matter I managed to figure it out in the end. As for 'authenticateWithCompletionHandler', I used this:
[[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:viewController animated:YES completion:nil];
I have the following code that I have been using before to handle invitations:
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
// Insert game-specific code here to clean up any game in progress.
if (acceptedInvite) {
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite];
mmvc.matchmakerDelegate = self;
[self presentViewController:mmvc animated:YES completion:nil];
}
};
However, now it has become deprecated in iOS 7. Where and how do I register a GameKit invite handler in my project?
GKInviteEventHandler to the rescue, and in particular take a look at GKLocalPlayerListener.
Conform to the GKLocalPlayerListener protocol and you should be OK. Below are the protocol methods, which look to be the intended replacement for invitationHandler, but split up in two parts.
- (void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite
- (void)player:(GKPlayer *)player didRequestMatchWithPlayers:(NSArray *)playerIDsToInvite
After you set up some object to conform to that, you just make a call to registerListener:.
[[GKLocalPlayer localPlayer] registerListener:yourObjectHere]
Don't worry about registering it as soon as possible, as the system caches the invites/challenges/turn based stuff, if there's no one to handle those and lets your listener know as soon as you set it up.
As Sam says the new way to do this is with the GKLocalPlayerListener protocol. The approach is reversed now. In the past you issued invitations to other players from part of your app. The other part listened for an invitation from another player and responded to that. Now, you use a matchMakerViewController or Game Center to issue invitations (as before) but now you listen for the acceptance of those invitations. After that Game Center calls didFindMatch to get everything started. If you are in receipt of an invitation Game Center starts your game and then calls didFindMatch to start it up.
This is my code:
In my .h file the GKLocalPlayerListener protocol:
#interface MNFStartupViewController : UIViewController<ADBannerViewDelegate, GKMatchmakerViewControllerDelegate, GKMatchDelegate, GKLocalPlayerListener, UIAlertViewDelegate>
in the .m file in my authenticateHandler block after the local player is authenticated:
[[GKLocalPlayer localPlayer] registerListener:self];
Then the method to listen for the acceptance of an invite:
-(void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite{
//Called when another player accepts a match invite from the local player.
NSLog(#"didAcceptInvite was called: Player: %# accepted our invitation", player);
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:invite];
mmvc.matchmakerDelegate = self;
[self presentViewController:mmvc animated:YES completion:nil];}
Now the method to start the game from Game Center with a set of players chosen in Game Center. This is hard to debug because you cannot start the game in Game Center whilst running it from Xcode at the same time (I don't think so anyway!) so there is a debugging AlertView that can be dropped.
-(void)player:(GKPlayer *)player didRequestMatchWithPlayers:(NSArray *)playerIDsToInvite{
//Called when the local player starts a match with another player from Game Center
//Start of debugging logging and alerting
NSLog(#"In didRequestMatchWithPlayers for players: %#", playerIDsToInvite);
NSString *logString = [[NSString alloc] initWithFormat:#"didrequestMatchWithPlayers was called with player IDs: %#", playerIDsToInvite];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Logging Alert" message:logString delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
//End of debugging logging and alerting
//Create a match for the chosen players
GKMatchRequest *match = [[GKMatchRequest alloc]init];
match.playersToInvite = playerIDsToInvite;
//Create a matchmaking viewcontroller for that match
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc]initWithMatchRequest:match];
mmvc.matchmakerDelegate = self;
[self presentViewController:mmvc animated:YES completion:nil];}
This is a method to kick off the whole matchmaking process:
-(IBAction)setupMatch:(id)sender{
GKMatchmakerViewController *matchViewController = [[GKMatchmakerViewController alloc] initWithMatchRequest:matchRequest];
matchViewController.matchmakerDelegate = self;
[self presentViewController:matchViewController animated:YES completion:nil];}
Finally this is the method called by Game Center to set the match going when all the players are connected and ready to go. currentPlayer, currentMatch and hostingPlayer are my own properties with obvious uses.
-(void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match{
//Called when GameCenter completes the auto matchmaking process
match.delegate = (id)self;
[self setCurrentMatch:match];
[self setCurrentPlayers:match.playerIDs];
NSLog(#"Match was found with players: %#, time to get on with the game.", self.currentPlayers);
//Use the built in features to decide which device should be the server.
self.hostingPlayer = [self chooseHostingPlayerIDfromPlayerIDs:self.currentPlayers];
[self dismissViewControllerAnimated:YES completion:nil];}
Hope it helps.
Also, invites only work on the devices. I could not get invites to work in the simulator in iOS 8.1.
I believe the answer is, annoyingly, different for a GKMatch and a GKTurnBasedMatch.
For a GKTurnBasedMatch the invitation counts as a turn event, and is handled in this function:
player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool)
That's inside the GKLocalPlayerListener protocol. You have to have officially registered GKLocalPlayerListener instance with your local player for this to work, so you can only do it after authentication.
...and it doesn't always work. Game Center is unreliable. But it does work sometimes, and that's... something?
I call Chartboost interstitial at applicationDidBecomeActive. My game use s Game Center and sometimes GC authorization window pops up under Chartboost interstitial, blocking Chartboost window. Only solution is switch to GameCenter and login there. Is it possible to check what authorization window was shown?
Blocking ads when Game Center login is on screen might be an option! Code works only on iOS6 btw
#interface ChartboostBridge : NSObject<ChartboostDelegate>
#end
#implementation ChartboostBridge
- (BOOL)shouldDisplayInterstitial:(NSString *)location{
NSLog(#"CB shouldDisplayInterstitial for %#",location);
if ([location isEqualToString:#"game_launch"]) {
if( [[GameCenterIos shared ] hasLogInView] ){
return NO;
}
}
return YES;
}
#end
#implementation GameCenterIos
- (BOOL)hasLogInView{
return isViewOnScreen;
}
- (void)login
{
GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];
if (localPlayer.isAuthenticated) {
isViewOnScreen=NO;
return;
}
localPlayer.authenticateHandler =
^(UIViewController *viewController,
NSError *error) {
if (localPlayer.authenticated) {
isAuthenticated = YES;
isViewOnScreen=NO;
} else if(viewController) {
NSLog(#"Game Center shows login ....");
isViewOnScreen=YES;
[self presentViewController:viewController];
} else {
NSLog(#"Game Center error or canceled login ....");
//User canceled Login view
isAuthenticated = NO;
isViewOnScreen=NO;
}
};
}
#pragma mark UIViewController stuff
-(UIViewController*) getRootViewController {
return [UIApplication
sharedApplication].keyWindow.rootViewController;
}
-(void)presentViewController:(UIViewController*)vc {
UIViewController* rootVC = [self getRootViewController];
[rootVC presentViewController:vc animated:YES
completion:nil];
}
#end
This is and old question but I just had the same problem and found a workaround.
Change the modalPresentationStyle of the gamecenter loggin view (returned by the ios 6 authentication handler) to UIModalPresentationFullScreen.
On iphone the screen doesn't lock when gamecenter loggin and chartboost interstitial appear. Only on Ipad happens. And whats the difference? that in ipad the loggin is not fullscreen. So I tested changing it to fullscreen and now it works without locking =)
I have a project that uses the Twitter API to Tweet the users high score. That project is written using Cocos2d v1.1 and everything is working fine. I recently started a new project using Cocos2d v2.0 and attempted to use the same code from my other project to incorporate the same Twitter functionality but the viewcontroller will not appear when the Tweet button is pressed. Below is the code I'm using...
if ([TWTweetComposeViewController canSendTweet]) // Check if twitter is setup and reachable
{
CCLOG(#"Can Tweet");
TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init];
// set initial text
NSString *theTweet = #"The message..."];
[tweetViewController setInitialText:theTweet];
// setup completion handler
tweetViewController.completionHandler = ^(TWTweetComposeViewControllerResult result) {
if(result == TWTweetComposeViewControllerResultDone) {
// the user finished composing a tweet
} else if(result == TWTweetComposeViewControllerResultCancelled) {
// the user cancelled composing a tweet
}
[viewController dismissViewControllerAnimated:YES completion:nil];
};
// present view controller
[[[CCDirector sharedDirector] openGLView] addSubview:viewController.view];
[viewController presentViewController:tweetViewController animated:YES completion:nil];
}
else
{
// Twitter account not configured, inform the user
NSLog(#"You can't send a tweet right now, make sure your device has an internet connection and you have at least one Twitter account setup");
}
I've discovered that "openGLView" is now depreciated and I have replaced it with "view". This still does not work. The method is firing though. I've included a CCLOG that return the string "Can Tweet" and that is appearing in the output window. Does anyone have any suggestions on how to get this to work. Let me know if I need to provide more information.
Thanks
Use this code in cocos2d 2.0
AppController * app = (((AppController*) [UIApplication sharedApplication].delegate));
TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init];
[tweetViewController setInitialText:string];
[tweetViewController setCompletionHandler:^(TWTweetComposeViewControllerResult result) {
dispatch_async(dispatch_get_main_queue(), ^{
{
if (result == TWTweetComposeViewControllerResultDone)
{
//successful
[[NSNotificationCenter defaultCenter] postNotificationName:#"PPTweetSuccessful" object:nil];
}
else if(result == TWTweetComposeViewControllerResultCancelled)
{
//Cancelled
}
}
[app.navController dismissModalViewControllerAnimated:YES];
[tweetViewController release];
});
}];
[app.navController presentModalViewController:tweetViewController animated:YES];