Game Center achievements are rewarded every time - ios

I am adding achievements into an xCode project. The code below that I am using works fine in awarding the achievement but the only problem is that it is constantly being awarded in the background in the debug console. This is happening every time I load the game.
I also find that when the achievement is awarded for the very first time the completion banner is on repeat.
My question today is how do I edit the code to only award the achievement once, display the banner and then never appear again?
-(void)Scoring
{
ScoreNumber = ScoreNumber + AddedScore;
AddedScore = AddedScore - 1;
if (AddedScore < 0) {
AddedScore = 0;
}
Score.text = [NSString stringWithFormat:#"%i", ScoreNumber];
if (ScoreNumber > 110 && ScoreNumber < 1000) {
LevelNUmber = 2;
//self.view.backgroundColor = [UIColor greenColor];
GKAchievement *achievement= [[GKAchievement alloc] initWithIdentifier:#"_level1easy"];
achievement.percentComplete = 100.0;
achievement.showsCompletionBanner = YES;
if(achievement!= NULL)
{
NSArray *achievements = [NSArray arrayWithObjects:achievement, nil];
[GKAchievement reportAchievements:achievements withCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error in reporting achievements: %#", error);
} else {
NSLog(#"Achievement 1 Success");
}
}];
}
}

If I understand your question correctly, then you just need to save the state of your achievement somewhere, then check it when you go into your Scoring method. Maybe try saving in NSUserdefaults, for example;
NSUserDefaults *savedScoring = [NSUserDefaults standardUserDefaults];
[savedScoring setObject:self.showsCompletionBanner forKey:#"showsCompletionBanner"];
Then check this whenever you game loads.

Related

Sit-Up counter using CMDeviceMotion

I'm trying to replicate a fitness app similar to Runtastic's Fitness Apps.
Sit-Ups
This our first app that uses the phone’s built-in accelerometer to detect movement. You need to hold the phone against your chest then sit up quickly enough and high enough for the accelerometer to register the movement and the app to count 1 sit-up. Be sure to do a proper sit-up by going high enough!
I did a prototype app similar to this question here and tried to implement a way to count sit-ups.
- (void)viewDidLoad {
[super viewDidLoad];
int count = 0;
motionManager = [[CMMotionManager alloc]init];
if (motionManager.deviceMotionAvailable)
{
motionManager.deviceMotionUpdateInterval = 0.1;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
// Get the attitude of the device
CMAttitude *attitude = motion.attitude;
// Get the pitch (in radians) and convert to degrees.
double degree = attitude.pitch * 180.0/M_PI;
NSLog(#"%f", degree);
dispatch_async(dispatch_get_main_queue(), ^{
// Update some UI
if (degree >=75.0)
{
//it keeps counting if the condition is true!
count++;
self.lblCount.text = [NSString stringWithFormat:#"%i", count];
}
});
}];
NSLog(#"Device motion started");
}
else
{
NSLog(#"Device motion unavailable");
}
}
The if condition statement works, as if I place the device on my chest and do a proper sit-up, but the problem about this if statement is that it will just continue counting and I would want it to only count when the device has gone back to it's original position.
Can anyone come up with a logical implementation for this?
A simple boolean flag did the trick:
__block BOOL situp = NO;
if (!situp)
{
if (degree >=75.0)
{
count++;
self.lblCount.text = [NSString stringWithFormat:#"%i", count];
situp = YES;
}
}
else
{
if (degree <=10.0)
{
situp = NO;
}
}
Not the best logical implementation here, but it gets the job done...

Game Center leaderboard shows one result

I've sent scores to the leaderboard with different test accounts but when I try to see the leaderboard I can only see the score from the account that I'm logged in.
I used this code to send the scores :
- (void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier
{
GKScore *scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier: #"GHS"];
scoreReporter.value = score;
scoreReporter.context = 0;
[GKScore reportScores:#[scoreReporter] withCompletionHandler:^(NSError *error) {
if (error == nil) {
NSLog(#"Score reported successfully!");
} else {
NSLog(#"Unable to report score!");
}
}];
}
This is the code I'm using to show the leaderboard:
- (void)showLeaderboardOnViewController:(UIViewController*)viewController
{
GKGameCenterViewController *gameCenterController = [[GKGameCenterViewController alloc] init];
if (gameCenterController != nil) {
gameCenterController.gameCenterDelegate = self;
gameCenterController.viewState = GKGameCenterViewControllerStateLeaderboards;
gameCenterController.leaderboardIdentifier = _leaderboardIdentifier;
[viewController presentViewController: gameCenterController animated: YES completion:nil];
}
}
Maybe it's because it is sandboxed and stuff? is this normal maybe?
Thanks
I've had this issue before and after much searching it seems to be an error with sandbox accounts. I split my table into highest of all time, highest today and highest friend and in each case other higher scores from my other sandbox accounts were ignored. When I added another one of my accounts as a friend, they started sharing scores just fine.
I used code more or less identical to your own and submitted to the App Store where it was accepted, now it works fine with live accounts.

iOS: Iterating Through Scores & Adding Them Up?

I'm using the following method to retrieve the top 100 scores from one of my gamecenter leaderboards. Everything is working, correctly, except that as I retrieve a score, I'd like to add them up, so that once it is done, I have 1 total score.
How could I fix it?
- (void) retrieveTop100Scores {
GKLeaderboard *leaderboard1 = [[GKLeaderboard alloc] init];
leaderboard1.identifier = [Team currentTeam];
leaderboard1.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboard1.playerScope = GKLeaderboardPlayerScopeGlobal;
leaderboard1.range = NSMakeRange(1, 100);
[leaderboard1 loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
if (scores != nil) {
for (GKScore *score in scores) {
NSLog(#"%lld", score.value);
//Add them all up here?
}
}
}];
}
You can make a variable outside of the loop, and in each iteration, var+=score.value. Therefore after the iteration, the variable you build will contain the total score.

How to implement ranking in Game Center

I want to implement ranking by using Game Center.
So , I implement like this .
-(void)authenticateLocalPlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *vc,NSError *err){
[self setLastError:err];
if ([CCDirector sharedDirector].isPaused){
[[CCDirector sharedDirector] resume];
}
if (localPlayer.authenticated){
_gameCenterFeaturesEnabled = YES;
// get localplayer's score.
GKLeaderboard *board = [[GKLeaderboard alloc] init];
// make a query
board.timeScope = GKLeaderboardTimeScopeAllTime;
// I want to get all player's score.
board.playerScope = GKLeaderboardTimeScopeToday;
// set my game category.
board.category = #"com.nobinobiru.shooting";
// I want to show top 3 score data.
board.range = NSMakeRange(1, 3);
[board loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
NSString *s = [NSString stringWithFormat:#"%lld",board.localPlayerScore.value];
[ud setObject:[NSString stringWithFormat:#"%#",s] forKey:#"bestScore"];
if (scores){
// I want to 3 items but it returned only 1 item.
NSLog(#"score is %#",scores);
}
}];
}else if (vc){
[[CCDirector sharedDirector] pause];
[self presentViewController:vc];
}
};
}
Then, I create 3 sandbox's user account , and I test it.
But it always only show current user's best score.
I want to show 3 sandbox's data.
I don't know why it happened like that.
My code works well in not sandbox environment?
Do you have any idea?
Thanks in advance.
All.
It works perfectly after 6 hours...
I think that the reason why it happened is my created game center account was not reflect immediately.

iOS EKEvent Store recreating iCloud calendars in a loop, won't save local.

I'm having a strange issue EKEventStore, iCloud and local calendars. If iCloud is enabled the Calendar is created and events are saved into the calendar as you would expect. if iCloud is turned off and you try to save an event nothing happens, however the device continues to create iCloud calendars in a loop every 3-5 seconds until iCloud is turned back on and then all of those calendars flood into iCloud as duplicates. I'm using nearly the exact code that has been referenced here on SO many times as well as in Apples Docs. I'm completely stumped as to why it's not working and there seems to be very little documentation on EKEventStore in general.
//•••••••••••••••••••••••••••••••••••••••••••••••
#pragma mark – Save Event
//•••••••••••••••••••••••••••••••••••••••••••••••
-(void)saveEventWithDate:(NSDate *)startDate endDate:(NSDate *)endDate
{
AppData *theData = [self theAppData];
if([self checkIsDeviceVersionHigherThanRequiredVersion:#"6.0"]) {
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { // iOS 6 Support
if (granted){
NSLog(#"Access Granted");
} else {
NSLog(#"Access Not Granted");
}
}];
}
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([eventStore calendarWithIdentifier:[defaults objectForKey:#"My Calendar"]] != nil) // Calendar Existed
{
event.calendar = [eventStore calendarWithIdentifier:[defaults objectForKey:#"My Calendar"]];
NSLog(#"Calendar Existed");
} else { // Create Calendar
EKSource *theSource = nil;
for (EKSource* src in eventStore.sources) {
if ([src.title isEqualToString:#"iCloud"]) {
theSource = src;
break;
}
if (src.sourceType == EKSourceTypeLocal && theSource==nil) {
theSource = src;
break;
}
}
[self setupCalendarWithSource:theSource withEvent:event];
}
NSLog(#"Type of Event:%#",typeOfEvent);
if ([typeOfEvent isEqualToString:#"Hello"]) {
event.title = [NSString stringWithFormat:#"%# Hello",[theData.hello_info objectForKey:#"customer_name"]];
event.location = [NSString stringWithFormat:#"Phone #%#",[theData.hello_info objectForKey:#"customer_phone_number"]];
event.notes = [NSString stringWithFormat:#"Hello Issue: %#",[theData.hello_info objectForKey:#"hello_issue"]];
NSLog(#"Hello");
}
event.startDate = startDate;
event.endDate = endDate;
event.allDay = NO;
EKAlarm *alarm = [EKAlarm alarmWithRelativeOffset:-1800]; // Half Hour Before
event.alarms = [NSArray arrayWithObject:alarm];
[eventStore saveEvent:event span:EKSpanThisEvent error:nil];
SAFE_PERFORM_WITH_ARG(_delegate, #selector(wasScheduled), nil);
}
-(void)setupCalendarWithSource:(EKSource *)theSource withEvent:(EKEvent *)event {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
EKCalendar *cal;
cal = [EKCalendar calendarWithEventStore:eventStore];
cal.title = #"My Appointments";
cal.source = theSource;
[eventStore saveCalendar:cal commit:YES error:nil];
NSLog(#"cal id = %#", cal.calendarIdentifier);
NSString *calendar_id = cal.calendarIdentifier;
[defaults setObject:calendar_id forKey:#"My Calendar"];
event.calendar = cal;
}
I'm not sure why you get this behavior, but I think due to the fact that having disabled iCloud, the system cannot make queries on it and then queue creation requests that are resolved once you wake iCloud (but I'm assuming).
Anyway, the first solution that comes to my mind is to check if iCloud is active or not in this way
EKSource *defaultSource = [eventStore defaultCalendarForNewEvents].source;
if (defaultSource.sourceType == EKSourceTypeCalDAV)
NSLog(#"iCloud Enable");
else
NSLog(#"iCloud Disable");
this is done you can save your events to the default source properly and then keep the 2 calendars (the local one and the cloud one) synchronized with each other ...
Reactivation of iCloud will still be prompted to add all local calendars.
see also the second answer here Accessing programmatically created calendar on iOS device (which is where I got the idea ;) )
I hope I was helpful.
EDIT: Maybe is not necessary to create a second calendar... Try to change the source of the calendar from EKSourceTypeCalDAV to EKSourceTypeLocal ... don't forget to save calendar with commit "YES"
EDIT2: Ok just tested ...
substitute this:
} else { // Create Calendar
EKSource *theSource = nil;
for (EKSource* src in eventStore.sources) {
if ([src.title isEqualToString:#"iCloud"]) {
theSource = src;
break;
}
if (src.sourceType == EKSourceTypeLocal && theSource==nil) {
theSource = src;
break;
}
}
[self setupCalendarWithSource:theSource withEvent:event];
}
with this ...
} else { // Create Calendar
EKSource *theSource = [eventStore defaultCalendarForNewEvents].source;
[self setupCalendarWithSource:theSource withEvent:event];
}
this way u will create the calendar in the right source (local if user deactivate iCloud and CalDAV otherwise)
then:
1) when user choose to deactivate iCloud should leave calendars on iphone (and not delete) so u have the cloud calendar in the local source
2) when user choose to activate iCloud will merge his local calendars with the cloud and there u go!!
i hope this help
If you want a bullet proof way to find iCloud Calendar and revert back to local Calendar if the iCloud is disabled, use the code below. I have included some comments which may help:
for (EKSource *source in eventStore.sources) { //Check for iCloud
if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:#"iCloud"]) {
NSLog(#"Found iCloud Service."); //Found iCloud
if([source calendarsForEntityType:EKEntityTypeEvent].count>0){ //Check to see if Calendar is enabled on iCloud
NSLog(#"iCloud Calendar is Enabled."); //Calendar is Enabled
if([self saveEventCalendarWithSource:source]){
return YES;
}
}else{
NSLog(#"iCloud Calendar is Disabled."); //Calendar is Disabled
}
}
}
//If we are here it means that we did not find iCloud Source with iCloud Name. Now trying any CalDAV type to see if we can find it
for (EKSource *source in self.reminderStore.sources) { //Check for iCloud
if (source.sourceType == EKSourceTypeCalDAV) {
[self logData:#"Trying to save calendar in EKSourceTypeCalDAV Service."];
if([self saveEventCalendarWithSource:source]){
return YES;
}
}
}
//If we are here it means that we did not find iCloud and that means iCloud is not turned on. Use Local service now.
for (EKSource *source in self.reminderStore.sources) { //Look for Local Source
if (source.sourceType == EKSourceTypeLocal){ //Found Local Source
NSLog(#"Found Local Source.");
if([self saveEventCalendarWithSource:source]){
return YES;
}
}
}
Here's the code to save Calendar:
- (Boolean) saveEventCalendarWithSource:(EKSource *)source{
EKCalendar *Calendar = nil;
MyCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:eventStore];
MyCalendar.title = #"XXX";
MyCalendar.CGColor = [UIColor blueColor].CGColor;
MyCalendar.source = source;
NSError *err;
if([eventStore saveCalendar:MyCalendar commit:YES error:&err]){
if(MyCalendar.calendarIdentifier == nil){
NSLog(#"Could not save Calendar: %#",err);
return FALSE;
}
NSLog(#"Calendar Created. Here's the identifier %#",[MyCalendar calendarIdentifier]);
return TRUE;
}
NSLog(#"Could not create calendar! Reason:%#",err.description);
return FALSE;
}
Your post was a big help, i was struggling witt the exact same bug. Thank you !
I just made a small modifications, as the solution of using the defaultCalendarForNewEvents' source was not working in every situation : some source won't let you create new calendars in them.
I just check the number of calendars in my icloud source. If the count is zero, then the calendar sync is off, and I take the local source :
EKSource* localSource = nil;
EKSource* iCloudSource = nil;
for (EKSource* source in _eventStore.sources){
if (source.sourceType == EKSourceTypeLocal){
localSource = source;
}else if(source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:#"iCloud"]){
iCloudSource = source;
}
}
if (iCloudSource && [iCloudSource.calendars count] != 0) {
calendar.source = iCloudSource;
}else{
calendar.source = localSource;
}

Resources