Never finished [GKAchievement loadAchievementsWithCompletionHandler:] - iphone

I use the function [GKAchievement loadAchievementsWithCompletionHandler:] to restore the current player achievements in initialization. But the completionHander was never called.
- (void)loadAchievements
{
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error)
{
if (error == nil) // !!-- if a breakpoint is set here, it would never be reached
{
#synchronized(_achievementsDictionary)
{
for (GKAchievement* achievement in achievements)
[_achievementsDictionary setObject:achievement forKey:achievement.identifier];
NSLog(#"achievements loaded");
}
}
else
{
NSLog(#"Error in loading achievements: %#", error);
}
}];
}
However, a similar function, [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:] works well:
- (void) retrieveAchievmentMetadata
{
[GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:
^(NSArray *descriptions, NSError *error) {
if (error != nil)
{
NSLog(#"Error in loading achievement descriptions: %#", error);
}
if (descriptions != nil)
{
#synchronized(_achievementsMetaDataDictionary)
{
for (GKAchievementDescription* desc in descriptions)
{
_achievementsMetaDataDictionary[desc.identifier] = desc;
}
}
NSLog(#"achievement descriptions loaded");
}
}];
}
What might be the problem?

It comes a bit late, but maybe it helps someone else.
The fact is that GKAchievement loadAchievementsWithCompletionHandler: loads all the achievements which the local player made progress on. This means, if there are fresh achievements set up in the regarding iTunes Connect app (without any progress), they won't be loaded. Some progress has to be reported first!
On the other hand GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler: is meant to get all the information about every of the available achievements for the regarding iTunes Connect app. The description provides the identifier of the achievement, too.
For a fresh achievement the flow is the following:
Load the achievement description. (GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:)
Report some progress for the achievement to Game Center. The GKAchievement can be created based on data in GKAchievementDescription. (GKAchievementDescription reportAchievements:withCompletionHandler:)
From this point on, load the progress of the achievement to set up your app on start. (GKAchievement loadAchievementsWithCompletionHandler:)

Did you check that the returned descriptions NSArray does not have 0 elements?
if( !descriptions.count )
printf( "User has not submitted _any_ progress on _any_ achievements\n" ) ;
else for (GKAchievementDescription* desc in descriptions) ..
Note the descriptions array here only returns the collection of achievements this user has previously submitted progress on, not the array of all achievements ever registered on GameCenter for this app.

Related

Game Center Matchmaking GKTurnBasedMatch has significant lag (~1 min)

I'm implementing a turn-based game with multiplayer mode through gamecenter. I have 2 devices (1 ipad, 1 iphone) to test in sandbox mode which were working fine but lately it has started to struggle in auto matchmaking process. After I send the first turn from one user, the other device doesn't immediately recognize that game but opens up its own fresh game. Before it was able to immediately spot the game started in the other device and matchmaking was fairly straightforward. And I don't remember changing any parts relevant to matchmaking (NSCoding, GKTurnBasedEventHandler, GKTurnBasedMatchmakerViewControllerDelegate delegate methods etc).
Now I send the first turn from one device and need to wait around 1 min so the other device can successfully connect to that game. After connection occurs endTurnWithMatchData calls work without any problems, it can send and receive data within 1-2 secs. But it won't be a good UX if users start a fresh game and had to wait 1 min so another user can connect to his game. Has anyone been experiencing significant lag in auto matchmaking process? I didn't implement invitations yet, so I cannot check it. The matchdata I archive with NSKeyedArchiver seemed quite big, 3396 bytes, even for a fresh game with almost no data. And here are relevant parts of my code:
GameOptionsViewController:
- (void)turnBasedMatchmakerViewControllerWasCancelled:(GKTurnBasedMatchmakerViewController *)viewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFailWithError:(NSError *)error
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match
{
[self dismissViewControllerAnimated:NO completion:nil];
self.gcMatch = match;
[self performSegueWithIdentifier:#"GameMultiplayer" sender:self];
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"GameMultiplayer"])
{
GameViewController *GameVC = (GameViewController *)segue.destinationViewController;
[GameVC setGameMode:GAMEMODE_MULTIPLAYER_SAMEDEVICE];
//Multiplayer game it is
if(self.gcMatch != nil)
{
[GameVC setGameMode:GAMEMODE_MULTIPLAYER_GAMECENTER];
GameVC.gcMatchDelegate = self;
GameVC.gcMatch = self.gcMatch;
NSLog(#"Game OVC Segue: Match ID | %#", self.gcMatch.matchID);
}
}
else
{
...
}
}
GameViewController:
//This method is called according to user actions
//It's the only method I use to send data to other participant
-(void) sendCurrentGameDataWithNewTurn:(BOOL) newTurn
{
NSLog(#"Sending game data current participant : %#", gcMatch.currentParticipant.playerID);
//Update match data if it is corrupted anyhow
if (gcMatch.currentParticipant == nil)
{
[GKTurnBasedMatch loadMatchWithID:gcMatch.matchID withCompletionHandler:^(GKTurnBasedMatch *match, NSError *error)
{
if (error != nil)
{
NSLog(#"Error :%#", error);
return ;
}
[self sendCurrentGameDataWithNewTurn:newTurn];
}];
}
else
{
NSData *matchData = [NSKeyedArchiver archivedDataWithRootObject:game];
//Game advances to new player, buttons are disabled
if(newTurn)
{
NSLog(#"SENDING NEW TURN");
NSUInteger currentIndex = [gcMatch.participants
indexOfObject:gcMatch.currentParticipant];
GKTurnBasedParticipant *nextParticipant;
nextParticipant = [gcMatch.participants objectAtIndex:
((currentIndex + 1) % [gcMatch.participants count])];
[gcMatch endTurnWithNextParticipants:[NSArray arrayWithObject:nextParticipant] turnTimeout:GC_TURN_TIMEOUT matchData:matchData completionHandler:^(NSError *error) {
NSLog(#"Sent");
if (error) {
NSLog(#"SNT - %#", error);
}
}];
}
else
{
NSLog(#"ONLY UPDATING DATA");
[gcMatch saveCurrentTurnWithMatchData:matchData completionHandler:^(NSError *error) {
NSLog(#"Sent");
if (error) {
NSLog(#"OUD - %#", error);
}
}];
}
}
}
-(void) updateGameDataWithGCMatch
{
//Update whole game data
self.game = [NSKeyedUnarchiver unarchiveObjectWithData:self.gcMatch.matchData];
//Update game ui
...
}
-(void) handleTurnEventForMatch:(GKTurnBasedMatch *)match didBecomeActive:(BOOL)didBecomeActive
{
//Check if I got data for the currently active match that options vc forwarded me here, if not do some debug print and return
if(![self.gcMatch.matchID isEqual:match.matchID])
{
//For debugging reasons I skip if i get info for any previous match (other player quit etc)
NSLog(#"GCMatch matchID: %# match matchID: %#",self.gcMatch.matchID,match.matchID);
return;
}
NSLog(#"Turn event handle");
self.gcMatch = match;
if([match.currentParticipant.playerID isEqualToString: [GKLocalPlayer localPlayer].playerID ])
{
//Disable field buttons
[self setFieldButtonsEnabled:TRUE];
[self turnChangeAnimationFromLeftToRight:FALSE];
}
[self updateGameDataWithGCMatch];
}
As for your question:
I myself tempered with matchmaking over Game Center quite a bit and also experienced lags quite frequently, which have been proven to not have been caused by my site but by apples game center servers.
As for additional guidance:
As far as I can see your current approach to matchmaking on a device is:
look if there is a match I can connect to --> If YES request gamedata and connect to the match ELSE start your own match and broadcast the matchdata
From my experience it is better practice to start with matchrequestbroadcasts, wait until you find a second player, define the server device (e.g. by lower checksum of game-center names) and then start the game on that device.

iOS Native Facebook Share no internet callback

I'm trying to implement the native facebook share for iOS 6 and need check if a share did succeed or not. This is the code I have used:
BOOL displayedNativeDialog =
[FBNativeDialogs
presentShareDialogModallyFrom:delegate
initialText:#"test"
image:nil
url:nil
handler:^(FBNativeDialogResult result, NSError *error) {
if (error) {
/* handle failure */
NSLog(#"error:%#, %#", error, [error localizedDescription]);
} else {
if (result == FBNativeDialogResultSucceeded) {
/* handle success */
NSLog(#"handle success");
} else {
/* handle user cancel */
NSLog(#"user cancel");
}
}
}];
if (!displayedNativeDialog) {
/* handle fallback to native dialog */
}
My problem is when I try this with no internet connection available I still get the FBNativeDialogResultSucceeded
It looks like you should get an error when no internet connection is available but it seems that it doesn't work like that.
If there are some solution where I don't need to use the reachability SDK that would be great.
You'll likely have to use the reachability SDK at this point. The Facebook SDK builds on top of the SLComposeViewController for the native functionality. That view controller returns two possible choices:
SLComposeViewControllerResultCancelled
SLComposeViewControllerResultDone
SLComposeViewControllerResultDone: The view controller is dismissed and the message is being sent in the background. This occurs when the user selects Done.
So since Facebook mirrors this the success case means the user clicked done and the message has been sent in the background.
However if you run this and there is no internet connection, the user should still see a pop-up indicating that the post could not be sent due to a connection failure.

Game Center achievement unlocking multiple times

I set up an achievement for passing the first level of my game and it works but when i replay the level and pass it it shows the notification banner again, how can i prevent this from happening?
Use this method to submit the achievement:
-(void) reportAchievementWithID:(NSString*) achievementID {
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) {
if(error) NSLog(#"error reporting ach");
for (GKAchievement *ach in achievements) {
if([ach.identifier isEqualToString:achievementID]) { //already submitted
return ;
}
}
GKAchievement *achievementToSend = [[GKAchievement alloc] initWithIdentifier:achievementID];
achievementToSend.percentComplete = 100;
achievementToSend.showsCompletionBanner = YES;
[achievementToSend reportAchievementWithCompletionHandler:NULL];
}];
}
Save that the user has passed the level to NSUserDefaults, then when the user passes the level check NSUserDefaults for your key, if it is there then don't do the achievement code for Game Center.

Game Center- handling failed achievement submissions?

Im a noob in game center # games generally. Im making my second game now and implemented the game center.
If the internet is available, there is no problem, everything works well.
But just now I purposely make the internet unreachable, and when I get an achievement, obviously it does not register to the Game Center's Achievement.
How and what's the best way to handle this issue?
Thank you....
You could add the GKAchievement objects that fail to register to an array and then resend them when you get back connectivity. You should also consider committing that array to persistent storage, in case your app terminates before it has a chance to send those achievements. Try this in your completion handler:
// Load or create array of leftover achievements
if (achievementsToReport == nil) {
achievementsToReport = [[NSKeyedUnarchiver unarchiveObjectWithFile:pathForFile(kAchievementsToReportFilename)] retain];
if (achievementsToReport == nil) {
achievementsToReport = [[NSMutableArray array] retain];
}
}
#synchronized(achievementsToReport) {
if(error == nil)
{
// Achievement reporting succeded
// Resend any leftover achievements
BOOL leftoverAchievementReported = NO;
while ([achievementsToReport count] != 0) {
[self resendAchievement:[achievementsToReport lastObject]];
[achievementsToReport removeLastObject];
leftoverAchievementReported = YES;
}
// Commit leftover achievements to persistent storage
if (leftoverAchievementReported == YES) {
[NSKeyedArchiver archiveRootObject:achievementsToReport toFile:pathForFile(kAchievementsToReportFilename)];
}
}
else
{
// Achievement reporting failed
[achievementsToReport addObject:theAchievement];
[NSKeyedArchiver archiveRootObject:achievementsToReport toFile:pathForFile(kAchievementsToReportFilename)];
}
}
Hope this helps.
I see that the two answers here focus on the specific mechanisms for archiving the undelivered achievement messages. For a higher-level description of the overall approach, you can see my answer to this related question: Robust Game Center Achievement code
Achievements (and all of the gamecenter stuff like leaderboard updates) conform to NSCoding. You can store them if you get an error submitting them and submit them later. This is what apple recommends in their docs.
Your application must handle errors when it fails to report progress to Game Center. For example, the device may not have had a network when you attempted to report the progress. The proper way for your application to handle network errors is to retain the achievement object (possibly adding it to an array). Then, your application needs to periodically attempt to report the progress until it is successfully reported. The GKAchievement class supports the NSCoding protocol to allow your application to archive an achievement object when it moves into the background.
from: http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/GameKit_Guide/Achievements/Achievements.html#//apple_ref/doc/uid/TP40008304-CH7-SW13
// Submit an achievement to the server and store if submission fails
- (void)submitAchievement:(GKAchievement *)achievement
{
if (achievement)
{
// Submit the achievement.
[achievement reportAchievementWithCompletionHandler: ^(NSError *error)
{
if (error)
{
// Store achievement to be submitted at a later time.
[self storeAchievement:achievement];
}
else
{
NSLog(#"Achievement %# Submitted..",achievement);
if ([storedAchievements objectForKey:achievement.identifier]) {
// Achievement is reported, remove from store.
[storedAchievements removeObjectForKey:achievement.identifier];
}
[self resubmitStoredAchievements];
}
}];
}
}
In case anyone stumbles upon this question in the future, Apple now has sample code for submitting achievements that includes a way to archive achievements that failed to submit (due to no network connection, etc). You'll find it in the Game Center programming guide.

Game Center iPhone - Load Achievement Progress

I have my app give a notification when a Game Center achievement is achieved/reaches 100%, however it shows the notification every times the user completes it, but I only want it to notify the first time in actually completed.
I found this in the Apple docs:
http://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/GameKit_Guide/Achievements/Achievements.html#//apple_ref/doc/uid/TP40008304-CH7-SW11
However didn't quite understand how it help fix my issue.
I only want to call this notification once, when the achievement is first achieved. So only show it if its not already achieved.
Edit
I have this to unlock the achievement:
- (void) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent
{
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
if (achievement)
{
achievement.percentComplete = percent;
[achievement reportAchievementWithCompletionHandler:^(NSError *error)
{
if (error != nil)
{
// Retain the achievement object and try again later (not shown).
}
}];
}
}
And unlock with this:
[self reportAchievementIdentifier:identifier percentComplete:percent];
Then show the notification with this line:
[[GKAchievementHandler defaultHandler] notifyAchievementTitle:#"Title" andMessage:#"Message"];
So do I simply need something like this in that code chunk?
if (achievement.completed != TRUE) {
[[GKAchievementHandler defaultHandler] notifyAchievementTitle:#"Title" andMessage:#"Message"];
}
There's a "completed"-property in GKAchievement... Try to make a new GKAchievement and check if it's not completed, then unlock it.