Game Center achievement unlocking more than once - ios

I have followed a great tutorial online on how to use Game Center in your iOS apps. It can be found here: http://code.tutsplus.com/tutorials/ios-sdk-game-center-achievements-and-leaderboards-part-2--mobile-5801
However the code for submitting an achievement seems to unlock achievements which have already been unlocked and I don't understand why. Here is my method which deals with a achievements:
-(void)submitAchievement:(NSString*)identifier percentComplete:(double)percentComplete {
if (self.earnedAchievementCache == NULL) {
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error == NULL) {
NSMutableDictionary *tempCache = [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement *score in tempCache) {
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache = tempCache;
[self submitAchievement:identifier percentComplete: percentComplete];
}
else {
// Something broke loading the achievement list. Error out, and we'll try again the next time achievements submit.
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: NULL error: error];
}
}];
}
else {
// Search the list for the ID we're using...
GKAchievement *achievement = [self.earnedAchievementCache objectForKey:identifier];
if (achievement != NULL) {
if ((achievement.percentComplete >= 100.0) || (achievement.percentComplete >= percentComplete)) {
// Achievement has already been earned so we're done.
achievement = NULL;
}
achievement.percentComplete = percentComplete;
}
else {
achievement = [[[GKAchievement alloc] initWithIdentifier:identifier] autorelease];
achievement.percentComplete = percentComplete;
// Add achievement to achievement cache...
[self.earnedAchievementCache setObject:achievement forKey:achievement.identifier];
}
if (achievement != NULL) {
// Submit the Achievement...
[achievement reportAchievementWithCompletionHandler: ^(NSError *error) {
[self callDelegateOnMainThread:#selector(achievementSubmitted:error:) withArg:achievement error:error];
}];
}
}
}
Thanks for your time, Dan.

Can you try this code and see the log. I added NSLog statements to see if the code detects achievement is completed and sets the achievement to nil. Also delete NULL from the code. Let me know how it works out.
-(void)submitAchievement:(NSString*)identifier percentComplete:(double)percentComplete {
if (!self.earnedAchievementCache) {
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (!error) {
NSMutableDictionary *tempCache = [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement *score in scores) { // the error is here
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache = tempCache;
[self submitAchievement:identifier percentComplete: percentComplete];
}
else {
// Something broke loading the achievement list. Error out, and we'll try again the next time achievements submit.
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: NULL error: error];
}
}];
}else {
// Search the list for the ID we're using...
GKAchievement *achievement = [self.earnedAchievementCache objectForKey:identifier];
NSLog(#"achievement %f",achievement.percentComplete);
if (achievement) {
if ((achievement.percentComplete >= 100.0) || (achievement.percentComplete >= percentComplete)) {
NSLog(#"Achievement has already been earned so we're done.");
achievement = nil;
}else{
achievement.percentComplete = percentComplete;
}
}else {
achievement = [[[GKAchievement alloc] initWithIdentifier:identifier] autorelease];
achievement.percentComplete = percentComplete;
// Add achievement to achievement cache...
[self.earnedAchievementCache setObject:achievement forKey:achievement.identifier];
}
if (achievement) {
NSLog(#"Submit the Achievement...");
[achievement reportAchievementWithCompletionHandler: ^(NSError *error) {
[self callDelegateOnMainThread:#selector(achievementSubmitted:error:) withArg:achievement error:error];
}];
}
}
}

Related

Facebook Photo Downloading

I am attempting to download Facebook albums of photos from a user in my app. Unfortunately although I do have an access token, I am getting zero albums from the requests. I am not getting an error, just getting zero. Why? If you would like to see any more code or ask more questions, just ask. Note that I have authorized the current user's Facebook permissions when they signed up, and I've since quit the app and opened it many times (don't think this would be an issue, since I have an access token..?)
- (void)getAlbums:(OLFacebookAlbumRequestHandler)handler {
if ([FBSDKAccessToken currentAccessToken]) {
// connection is open, perform the request
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSString *graphPath = #"me/albums?limit=100&fields=id,name,count,cover_photo";
if (self.after) {
graphPath = [graphPath stringByAppendingFormat:#"&after=%#", self.after];
}
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:graphPath parameters:nil];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (self.cancelled) {
return;
}
if (error) {
[OLFacebookAlbumRequest handleFacebookError:error completionHandler:handler];
return;
}
NSString *parsingErrorMessage = #"Failed to parse Facebook Response. Please check your internet connectivity and try again.";
NSError *parsingError = [NSError errorWithDomain:kOLErrorDomainFacebookImagePicker code:kOLErrorCodeFacebookImagePickerBadResponse userInfo:#{NSLocalizedDescriptionKey: parsingErrorMessage}];
id data = [result objectForKey:#"data"];
if (![data isKindOfClass:[NSArray class]]) {
handler(nil, parsingError, nil);
return;
}
NSMutableArray *albums = [[NSMutableArray alloc] init];
for (id album in data) {
if (![album isKindOfClass:[NSDictionary class]]) {
continue;
}
id albumId = [album objectForKey:#"id"];
id photoCount = [album objectForKey:#"count"];
id name = [album objectForKey:#"name"];
if (!([albumId isKindOfClass:[NSString class]] && [photoCount isKindOfClass:[NSNumber class]]
&& [name isKindOfClass:[NSString class]])) {
continue;
}
OLFacebookAlbum *album = [[OLFacebookAlbum alloc] init];
album.albumId = albumId;
album.photoCount = [photoCount unsignedIntegerValue];
album.name = name;
album.coverPhotoURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=small&access_token=%#", album.albumId, [FBSDKAccessToken currentAccessToken].tokenString]];
[albums addObject:album];
}
// get next page cursor
OLFacebookAlbumRequest *nextPageRequest = nil;
id paging = [result objectForKey:#"paging"];
if ([paging isKindOfClass:[NSDictionary class]]) {
id cursors = [paging objectForKey:#"cursors"];
id next = [paging objectForKey:#"next"]; // next will be non nil if a next page exists
if (next && [cursors isKindOfClass:[NSDictionary class]]) {
id after = [cursors objectForKey:#"after"];
if ([after isKindOfClass:[NSString class]]) {
nextPageRequest = [[OLFacebookAlbumRequest alloc] init];
nextPageRequest.after = after;
}
}
}
handler(albums, nil, nextPageRequest);
}];
}
else {
NSString *message = #"No Facebook user authentication found.";
handler(nil, [NSError errorWithDomain:kOLErrorDomainFacebookImagePicker code:kOLErrorCodeFacebookImagePickerNoOpenSession userInfo:#{NSLocalizedDescriptionKey: message}], nil);
}
}
//Code for fetching albums...
- (void)loadNextAlbumPage {
self.inProgressRequest = self.albumRequestForNextPage;
self.albumRequestForNextPage = nil;
[self.inProgressRequest getAlbums:^(NSArray/*<OLFacebookAlbum>*/ *albums, NSError *error, OLFacebookAlbumRequest *nextPageRequest) {
self.inProgressRequest = nil;
self.loadingIndicator.hidden = YES;
self.albumRequestForNextPage = nextPageRequest;
if (error) {
if (self.parentViewController.isBeingPresented) {
self.loadingIndicator.hidden = NO;
self.getAlbumError = error; // delay notification so that delegate can dismiss view controller safely if desired.
} else {
[self.delegate albumViewController:self didFailWithError:error];
}
return;
}
NSMutableArray *paths = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < albums.count; ++i) {
[paths addObject:[NSIndexPath indexPathForRow:self.albums.count + i inSection:0]];
}
[self.albums addObjectsFromArray:albums];
if (self.albums.count == albums.count) {
// first insert request
[self.tableView reloadData];
} else {
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
}
if (nextPageRequest) {
self.tableView.tableFooterView = self.loadingFooter;
} else {
self.tableView.tableFooterView = nil;
}
}];
}
//And when they signed up:
[[[FBSDKGraphRequest alloc] initWithGraphPath:#"me" parameters:#{ #"fields" : #"id,first_name,photos,picture.width(400).height(400)"}]
startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (!error) { //etc etc the method continues.
FB authentication will give you a unique access_token for a particular set of permissions. To access user photos, you need to request the user_photos permission. Use the FBSDKLoginButton to request permissions.
loginButton.readPermissions = #[#"public_profile", #"email", #"user_photos"];
Once you have an access token with the required permissions, persist that locally (on the device) to reuse in future. If the access code is still valid, you won't need to request it again. If it becomes invalid (in case the user explicitly revoked permissions to your app), send them back to the login screen.

Why I can't connect 2 players in the same room on a GameCenter turn-based game?

I'm trying to create a Turn-Based GameCenter multiplayer game and I can't connect 2 players in a match:
I'm connecting with a custom UI and both devices (iPhone and simulator) are running iOS 9.1.
I run on a device and after I run findMatch it creates a new room with me and a player with playerID nil which I read it's normal but when I run on the other device it also does same stuff. Devices are not in same room.
What am I doing wrong?
I am using the following code:
-(void)searchForTurnBasedMatch{
GKMatchRequest *request = [[GKMatchRequest alloc] init];
// request.minPlayers = 2;
request.maxPlayers = 4;
request.inviteeResponseHandler = ^(NSString *playerID, GKInviteeResponse response)
{
if (response == GKInviteeResponseAccepted)
{
NSLog(#"playerCeva");
}
};
[GKTurnBasedMatch findMatchForRequest:request withCompletionHandler:^(GKTurnBasedMatch * _Nullable match, NSError * _Nullable error) {
if (error)
{
NSLog(#"eroare la gasire meci: %#",error);
}
else if (match != nil)
{
self.myTurnBasedMatch = match;
[self resumeMatch:match];
}
}];
}
- (void)resumeMatch:(GKTurnBasedMatch *)match {
self.myTurnBasedMatch = match;
NSLog(#"Created/Selected match");
NSLog(#"ID: %#\tCreated: %#", match.matchID, match.creationDate);
GKTurnBasedParticipant *firstParticipant = [match.participants objectAtIndex:0];
if (firstParticipant.lastTurnDate != NULL) {
if ([match.currentParticipant.playerID isEqualToString:[GKLocalPlayer localPlayer].playerID]) {
NSLog(#"Taking turn ...");
} else {
NSLog(#"Not my turn ...");
}
} else {
NSLog(#"New game ...");
}
}

Why is my code being ran backwards? Or so it seems?

I have an IBAction that when called, registers a user information with Parse.com... I have only implemented the email and username methods, since i need to make sure that prior to submission to the server i added a counter int, if the counter == 2 it will execute the registration... well to my surprise, when I run the code, it gets ran backwards, so my conditional statement at the bottom is not even reviewed, why is this the case?
This is my code:
- (IBAction)signMeUpButton:(id)sender {
[self.view endEditing:YES];
counter = 0;
user = [PFUser user];
NSString *emailFromTextField = self.emailTF.text;
if ([self isValidEmailAddress:emailFromTextField]) {
[self emailHasBeenTaken:emailFromTextField completion:^(BOOL emailIsTaken, NSError *error) {
if (error) {
// TODO: handle any errors here
return;
}
if (!emailIsTaken) {
emailString = emailFromTextField;
user.email = emailString;
counter++;
NSLog(#"The email is %# the counter is %i", emailString, counter);
}
else {
[self duplicateEmail];
}
}];
}
NSString *usernameFromTextField = self.usernameTF.text;
if (usernameFromTextField.length >= 1) {
[self usernameHasBeenTaken:usernameFromTextField completion:^(BOOL usernameIsTaken, NSError *error) {
if (error) {
return;
}
if (!usernameIsTaken) {
usernameString = usernameFromTextField;
user.username = usernameString;
counter++;
NSLog(#"The username is %# and the counter is %i", usernameString, counter);
}
else {
//
}
}];
}
if (counter == 2) {
NSLog(#"Its working");
}
}
And this is what I get on my console when i run it, as you can see the usernameHasBeenTaken is being called FIRST, even though I have it written second... why is that?
2014-07-17 23:18:12.169 app[28210:60b] in the usernameHasBeenTaken. USERNAME IS NOT EXISTENT
2014-07-17 23:18:12.170 app[28210:60b] The username is sample and the counter is 1
2014-07-17 23:18:15.328 app[28210:60b] in the emailHasBeenTaken, EMAIL IS NOT EXISTENT
2014-07-17 23:18:15.328 app[28210:60b] The email is sample#email.com the counter is 2
These are my 2 methods: usernameHasBeenTaken and emailHasBeenTaken
- (void)emailHasBeenTaken:(NSString *)email completion:(void(^)(BOOL emailIsTaken, NSError *error))completionBlock
{
void (^completionCopy)(BOOL, NSError *) = [completionBlock copy];
PFQuery *query = [PFUser query];
[query whereKey:#"email" equalTo:email];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"in the emailHasbeenTaken ERROR HAS OCCURRED");
if (completionCopy) {
completionCopy(NO, error);
}
return;
}
if (objects.count > 0) {
NSLog(#"in the emailHasbeenTaken EMAIL IS DUPLICATE");
if (completionCopy) {
completionCopy(YES, nil);
}
}
else {
NSLog(#"in the emailHasBeenTaken, EMAIL IS NOT EXISTENT");
if (completionCopy) {
completionCopy(NO, nil);
}
}
}];
}
- (void) usernameHasBeenTaken:(NSString *)username completion:(void(^)(BOOL usernameIsTaken, NSError *error))completionBlock
{
void (^completionCopy)(BOOL, NSError *) = [completionBlock copy];
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:username];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"in the usernameHasBeenTaken ERROR HAS OCCURED");
if (completionCopy) {
completionCopy(NO, error);
}
return;
}
if (objects.count > 0) {
NSLog(#"in the usernameHasBeenTaken USERNAME IS DUPLICATE");
if (completionCopy) {
completionCopy(NO, nil);
}
}
else {
NSLog(#"in the usernameHasBeenTaken. USERNAME IS NOT EXISTENT");
if (completionCopy) {
completionCopy(NO, nil);
}
}
}];
}
Your use of a completion block indicates that you are running them asynchronously. If that is the case then your password condition gets hit immediately after you invoke emailHasBeenTaken:completion:
If you must run async, which you should if you are hitting a web service, you need to nest your calls in the completion blocks.
Edit
It appears that this is the case. The Parse API runs async, to avoid locking the UI thread. You will need to nest your callbacks like I show below.
- (IBAction)signMeUpButton:(id)sender {
[self.view endEditing:YES];
counter = 0;
user = [PFUser user];
NSString *emailFromTextField = self.emailTF.text;
if ([self isValidEmailAddress:emailFromTextField]) {
[self emailHasBeenTaken:emailFromTextField completion:^(BOOL emailIsTaken, NSError *error) {
if (error) {
// TODO: handle any errors here
return;
}
if (!emailIsTaken) {
emailString = emailFromTextField;
user.email = emailString;
counter++;
NSLog(#"The email is %# the counter is %i", emailString, counter);
NSString *usernameFromTextField = self.usernameTF.text;
if (usernameFromTextField.length >= 1) {
[self usernameHasBeenTaken:usernameFromTextField completion:^(BOOL usernameIsTaken, NSError *error) {
if (error) {
return;
}
if (!usernameIsTaken) {
usernameString = usernameFromTextField;
user.username = usernameString;
counter++;
NSLog(#"The username is %# and the counter is %i", usernameString, counter);
}
else {
//
}
if (counter == 2) {
NSLog(#"Its working");
}
}];
}
}
else {
[self duplicateEmail];
}
}];
}
}

reportAchievementWithCompletionHandler deprecated in iOS7 but replacement not specified

Has anyone found the replacement for
[GKAchievement reportAchievementWithCompletionHandler]?
Typically when things are deprecated the docs indicate a replacement. Not so with this one so far and I wanted to cross this off the list of possible causes of another issue we are seeing.
Was looking for the same info and saw your post, here is what I went with after not finding anything either:
NSArray *achievements = [NSArray arrayWithObjects:achievement, nil];
[GKAchievement reportAchievements:achievements withCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error in reporting achievements: %#", error);
}
}];
Here is apple's full code (same/similar to Silly Goose's Answer)
- (void) completeMultipleAchievements
{
GKAchievement *achievement1 = [[GKAchievement alloc] initWithIdentifier: #"DefeatedFinalBoss"];
GKAchievement *achievement2 = [[GKAchievement alloc] initWithIdentifier: #"FinishedTheGame"];
GKAchievement *achievement3 = [[GKAchievement alloc] initWithIdentifier: #"PlayerIsAwesome"];
achievement1.percentComplete = 100.0;
achievement2.percentComplete = 100.0;
achievement3.percentComplete = 100.0;
NSArray *achievementsToComplete = [NSArray arrayWithObjects:achievement1,achievement2,achievement3, nil];
[GKAchievement reportAchievements: achievementsToComplete withCompletionHandler:^(NSError *error)
{
if (error != nil)
{
NSLog(#"Error in reporting achievements: %#", error);
}
}];
}
This works in iOS7 with no issues.
- (void)checkAchievements
{
if(myScore >= 25000){
GKAchievement *achievement= [[GKAchievement alloc] initWithIdentifier:#"Achiev1"];
achievement.percentComplete = 100.0;
achievement.showsCompletionBanner = YES;
[self Achievements:achievement];
}
}
-(void)Achievements:(GKAchievement*)achievement {
NSArray *achievements = [NSArray arrayWithObjects:achievement, nil];
[GKAchievement reportAchievements:achievements withCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error in reporting achievements: %#", error);
}
}];
}

load Game Center friends and their scores into UITableView

So I was wondering after reading the apple docs(https://developer.apple.com/library/ios/documentation/GameKit/Reference/GKLeaderboard_Ref/Reference/Reference.html#//apple_ref/occ/instp/GKLeaderboard/category) how would one create a UITableView and fill it with the localPlayers Game Center friends and there scores in a specific leaderboard. I know how to get the friends list and friends scores individually by using the loadScoresWithCompletionHandler: method.
Edit: So far I got this to get individual friends photo, score and displayname saved into one NSArray. But i can't figure out how to disply them in a UITableView.
- (void) loadPlayerData: (NSArray *) identifiers
{
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
if (leaderboardRequest != nil) {
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeFriendsOnly;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboardRequest.category = #"MJ_IL";
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil) {
// handle the error. if (scores != nil)
}
if (scores != nil){
for (GKScore* score in scores) {
NSArray *playerIdArray = [NSArray arrayWithObject:score.playerID];
[GKPlayer loadPlayersForIdentifiers:playerIdArray withCompletionHandler:^(NSArray *players, NSError *error) {
GKPlayer *player = [players objectAtIndex:0];
[player loadPhotoForSize:GKPhotoSizeSmall withCompletionHandler:^(UIImage *photo, NSError *error) {
if (score.playerID == player.playerID) {
if (photo != nil) {
playerInfo = [NSArray arrayWithObjects:score, player.displayName, photo, nil];
} else if (photo == nil) {
playerInfo = [NSArray arrayWithObjects:score, player.displayName, nil];
}
if (error != nil) {
NSLog(#"%#", error.localizedDescription);
}
}
}];
}];
}
}
}];
}
}
- (void)compareLocalPlayerScoreWithFriends {
GKScore *friendScore = [playerInfo objectAtIndex:0];
NSString *friendDisplayName = [playerInfo objectAtIndex:1];
if ([playerInfo objectAtIndex:2] != nil) {
UIImage *friendPhoto = [playerInfo objectAtIndex:2];
if (friendScore.value > interactiveHighscore) {
[friendNameLabel setText:friendDisplayName];
[friendScoreLabel setText:(NSString *)friendScore];
friendImageView.image = friendPhoto;
}
}
}
Thanks guys,
Georges
Take a look at this tutorial by Ray Wenderlich, it explains how to display simple pictures and text in a UITableView - there are three parts and should get you working with, at least, a basic but working view.
At its very core level this is the code that does "the work" for displaying in a UITableView
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:#"MyBasicCell"];
ScaryBugDoc *bug = [self.bugs objectAtIndex:indexPath.row];
cell.textLabel.text = bug.data.title;
cell.imageView.image = bug.thumbImage;
return cell;
}
Update
Here is my code for generating leaderbord data with alias and photos, hope you can modify it appropriately but shouldnt be too different
-(void)getScoresAndAliasForLeaderboard:(GKLeaderboard *)leaderboardRequest{
if (leaderboardRequest == nil)
{
leaderboardRequest = [[GKLeaderboard alloc] init];
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeFriendsOnly;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboardRequest.category = #"HighScore";
leaderboardRequest.range = NSMakeRange(1,100);
}
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
// Handle the error.
}
if (scores != nil)
{
NSMutableArray *retrievePlayerIDs = [[NSMutableArray alloc] init];
for (GKScore *s in scores)
{
[retrievePlayerIDs addObject:s.playerID];
GCLeaderboardScore *playerScore = [[GCLeaderboardScore alloc] init];
playerScore->playerID = s.playerID;
playerScore->score = (int)s.value;
playerScore->rank = s.rank;
playerScores[s.playerID] = playerScore; //playerScores is a NSMutableDictionary
if ([s.playerID isEqualToString: leaderboardRequest.localPlayerScore.playerID]){
me = playerScore;
}
}
if (me == nil){
me = [[GCLeaderboardScore alloc] init];
me->playerID = leaderboardRequest.localPlayerScore.playerID;
me->score = leaderboardRequest.localPlayerScore.value;
me->alias = #"Me";
playerScores[me->playerID] = me;
}
[GKPlayer loadPlayersForIdentifiers:retrievePlayerIDs withCompletionHandler:^(NSArray *playerArray, NSError *error)
{
for (GKPlayer* p in playerArray)
{
GCLeaderboardScore *playerScore = playerScores[p.playerID];
playerScore->alias = p.alias;
[p loadPhotoForSize:GKPhotoSizeSmall withCompletionHandler:^(UIImage *photo, NSError *error) {
if (photo != nil) {
playerScore->photo = photo;
}
else{
playerScore->photo = [UIImage imageNamed:#"wordpress_avatar.jpg"];
}
if (error != nil) {
NSLog(#"%#", error.localizedDescription);
}
}];
}
}];
}
}];
}

Resources