I'm trying to implement Game Center invites in a 2-person realtime game. Since invites are not supported in the simulator, I'm testing this on one device running iOS5 and a second one running iOS6 (this is done on purpose).
If I'm using the old-fashioned built-in GKMatchmakerViewController UI on either device to initiate the invite, it works fine both ways - when the iOS5 device initiates the invite as well as when the iOS6 device initiates it.
However, in iOS6 I want to use my own UI to select the player to invite, so I'm using GKMatchRequest to programmatically issue the invite, setting the playersToInvite attribute.
The problem is, the other (iOS5) device gets the push notification, launches the application, runs the [GKMatchmaker sharedMatchmaker].inviteHandler, shows the Game Center UI with the invite details, but even when the iOS6 device sends a finishMatchmakingForMatch request - the iOS5 device doesn't proceed any further. No other handler / delegate is called on the iOS5 machine, no GKMatch object is returned, and it continues to show the Game Center UI with both players marked as "Ready" and with a message saying "Waiting for [iOS6 player] to start the game". The only button on this UI is a Cancel button.
Here's the code snippet that sends the invitation on the iOS6 machine:
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = [NSArray arrayWithObject:playerID];
request.inviteMessage = message;
request.inviteeResponseHandler = ^(NSString *playerID, GKInviteeResponse response)
{
if (response == GKInviteeResponseAccepted)
[[GKMatchmaker sharedMatchmaker] finishMatchmakingForMatch:self.match];
};
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request withCompletionHandler:^(GKMatch *match, NSError *error)
{
... [whatever]
}];
And here's the code snippet for the invite handler on the iOS5 machine:
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite)
{
if (acceptedInvite)
{
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
mmvc.matchmakerDelegate = self;
[navController presentModalViewController:mmvc animated:YES];
});
else if (playersToInvite)
{
... [whatever]
}
}
The sequence is as follows:
iOS6 sends a findMatchForRequest request with the iOS5 player id.
A push notification is shown on the iOS5 machine.
The application is launched on the iOS5 machine and the inviteHandler is called.
The GKMatchmakerViewController is shown on the iOS5 machine with the invite details, and the iOS6 user has a spinning "Connecting" status.
The inviteeResponseHandler on the iOS6 machine is called and sends a finishMatchmakingForMatch request.
The status of the iOS6 user in the iOS5 Game Center screen changes from spinning "Connecting" to "Ready", and at this point both players are marked as "Ready".
The iOS6 machine gets a match: player: didChangeState: callback, showing the iOS5 player as GKPlayerStateConnected, so as far as the iOS6 machine is concerned the match process is finished and the game can begin.
Nothing whatsoever happens from now on on the iOS5 machine. It is stuck with "Waiting for [iOS6 user] to start the game" until it is cancelled by a timeout. It never receives any GKMatch object at any point, so it cannot start the game.
Since things work fine if I use the standard Game Center UI on the iOS6 machine rather than a programmable invite, it means that the standard UI must do something more to tell the other machine that the game has to start. However, I browsed through all the relevant Game Center objects and couldn't find anything else to send.
I should mention again that the reverse configuration (iOS5 initiating the invite using the standard UI) works fine on both machines.
Help, anyone?
I am having a similar problem. One of the things I did to partially solve is to use programmatic for ios6 users, and viewController for ios5 users. I think you are doing that too but your inviteHandler code only seems to have viewController code. Were you able to fully solve your issue?
Related
I'm working on an OS X application after having worked almost exclusively on iOS apps. If the user quits, say by pressing Command-Q, how can I store some data locally and send some data to my server before the app truly goes out of memory?
On iOS I typically launch a background event when I detect the app going into the background. I've tried listening for "ApplicationWillTerminate" but it doesn't seem to afford me the time to send data.
Should I be intercepting the menu action and performing my work before quitting?
I realize this is a pretty basic question, but my Google-Foo has not led me to a straightforward answer.
applicationWillTerminate is indeed the method to clean up things before the app quits.
Alternatively for an asynchronous way implement applicationShouldTerminate, start you task to store and send the data asynchronously and return NSTerminateLater.
When the task is completed call
[NSApp replyToApplicationShouldTerminate:YES];
Here a snippet as example
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
[self startTaskToSendDataWithCompletion:^() {
[NSApp replyToApplicationShouldTerminate:YES];
}];
return NSTerminateLater;
}
In my app, I use the following code in the view controller in my sprite kit project to authenticate a player using GameKit:
-(void)autheticateLocalPlayer{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
if (error != nil){
NSLog(#"%#", [error localizedDescription]);
}
if (viewController != nil){
[self presentViewController:viewController animated:YES completion:nil];
}else{
if ([GKLocalPlayer localPlayer].authenticated){
_gameCenterEnabled = YES;
[[GKLocalPlayer localPlayer]loadDefaultLeaderboardIdentifierWithCompletionHandler:^(NSString *leaderboardIdentifier, NSError *error) {
if (error != nil){
NSLog(#"%#", [error localizedDescription]);
}
else{
_leaderboardIdentifier = leaderboardIdentifier;
}
}];
}
else{
_gameCenterEnabled = NO;
NSLog(#"game center not available");
}
}
};
}
It works fine on my phone, but after building an Ad-Hoc build with a provisioning profile including a friend's phone to see how it would work, they were not asked to log in when they opened the app, whereas my phone was (and all simulator phones were).
On top of that, attempting to open the leaderboards would simply show a message "game center is not available, user is not logged in" or something like that.
I went to this friend's phone's Game Center settings, and verified that sandbox mode was ON. After that, I manually logged in to a Sandbox tester account I made in iTunes connect, and once I opened my app again, (on their phone), it displayed the "welcome back" message. However, the leaderboards showed "no scores", when I believe that my score should be on there.
My questions are:
1. Why were they not asked to log in? Is it a problem with my code, or is that how it's supposed to work when beta testing with sandbox mode on?
2. Why doesn't it show the other scores? My leaderboard is in iTunes connect, and the leaderboard name is properly displayed in the app, but no score are visible.
Thanks for any help!
Apparently, it takes about 10 hours before the other user's scores will be updated. Today, all of my test users' scores are on game center. Would be nice if this was made more obvious, so developers didn't think Game Center wasn't working.
I am using Core Bluetooth in my project. I have included Session Backgrounding to avail its background mode functionality. I have observed that the delegate for peripheral disconnection,
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
gets called in the background mode. However any code I write in this method is not executed except for NSLogs. Can somebody explain exactly what kind of code can be executed here?
My aim is to send this disconnection notification to my server.
Ok it seems it was some issue at my end. According the the documentation your app is woken (in the background) for around 10 seconds when it gets a bluetooth related delegate call.
You can use this time to perform any non view updating task and even request for additional time using beginBackgroundTaskWithExpirationHandler.
My code looks like this.
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
[self sendEmailInBackground:peripheral]; //Code to send a server request
return;
}
and its working in the background mode. This also works when the phone is in lock state.
I'm trying to use the new Mountain Lion NSUserNotificationCenter for my application (which isn't too hard actually). Posting notifications works like a charm via
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
userNotification.title = #"Some title";
userNotification.informativeText = #"Some text";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
However, i'd like to dismiss all notifications that are on the screen once the app gains focus. E.g. like the new Messages app does it. When new messages are received in the background, notifications are shown. When the app becomes active again, these are dismissed automatically and vanish from the screen and from the Notification Center.
To replicate this, I've registered a method to the NSApplicationDidBecomeActiveNotification notification which also gets called succesfully. In there I call [NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications].
This, however, has the effect that notifications that have been collected in the Notification Center are removed while the corresponding "bubbles" that are displayed in the top right corner are still displayed.
Iterating all delivered notifications and removing them each on their own has the exactly same effect, as has using scheduleNotification instead of deliverNotification.
Am I the only one experiencing this, or am I missing something to dismiss the on-screen part and the Notification Center part of a notification programatically?
The Messages app is probably using the private NSUserNotificationCenter _removeAllDisplayedNotifications or _removeDisplayedNotification: method.
You can try to use these methods to test if this is what you are looking for. Just add this category interface to declare the methods:
#interface NSUserNotificationCenter (Private)
- (void)_removeAllDisplayedNotifications;
- (void)_removeDisplayedNotification:(NSUserNotification *)notification;
#end
Unfortunately, since these are undocumented methods, you can not use them in an app distributed through the App Store. If this is indeed what you are looking for, then you should file a bug and ask for these methods to become part of the public API.
As of 10.9, the following methods remove any displayed notifications:
// Clear a delivered notification from the notification center. If the
// notification is not in the delivered list, nothing happens.
- (void)removeDeliveredNotification:(NSUserNotification *)notification;
// Clear all delivered notifications for this application from the
// notification center.
- (void)removeAllDeliveredNotifications;
The behavior seems to have changed since 10.8, as any displayed notifications are removed as well when these methods are called (thanks #0xced for clarification).
removeDeliveredNotification is removing the displayed notification for me (on 10.11), the caveat being the identifier on the notification must be set.
Hmmm……
A question about UILocalNotification and the notificaton's alertLaunchImage.
My app uses UILocalNotifiaction(s) to get users' attention. As usual, an alert is presented with "Action" and "Close" buttons. When the user taps Action, the image specified by alertLaunchImage is presented. The alertLaunchImage is a screenshot of of one of the views of the app which is shown after the data is initialized when launched normally.
Here are the 3 cases when the notification is delivered:
App is running in foreground - no alert, no launchImage is shown as designed. No problems.
If my app is running in background when the notification is delivered, the launchImage works like a charm. No problems. The launchImage with no app-related data is shown and then the app fills up the data. This part works seamlessly.
However, if the app is not running when the notification is delivered, the sequence is confusing - or I missed something. The app gets launched and shows the alertLaunchImage instead of the Default image. Then is goes thru several other screens (as part of initialization and data processing) before the actual screen (live version of alertLaunchImage) is shown.
This can get very confusing to the user. My question comes in here. How can this be avoided?
R/-
Sam.!
you can try cleaning up the alert view settings in applicationWillTerminate:
According to the UIApplicationDelegate reference applicationWillTerminate::
"This method lets your application know
that it is about to be terminated and
purged from memory entirely. You
should use this method to perform any
final clean-up tasks for your
application, such as freeing shared
resources, saving user data,
invalidating timers, and storing
enough application state to
reconstitute your application’s
interface when it is relaunched"
HTH,
Oded
If your app is launched by a local notification, you will receive that notification in the options passed to -application:didFinishLaunchingWithOptions:. Based on that, you can write code that navigates to the correct screen without animations.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UILocalNotification *localNotification = [launchOptions valueForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification != nil) {
// startup by local notification
} else {
// normal startup
}
}