Instance method not found callDelegateOnMainThread - ios

I'm getting the error instance method '-callDelegateOnMainThread:withArg:error:' not found what should I do here? I merged in some achievement code into Ray Wenderlichs turn based multiplayer code.
The functions are part of the following interface:
#interface GCTurnBasedMatchHelper : NSObject <GKTurnBasedMatchmakerViewControllerDelegate, GKTurnBasedEventHandlerDelegate, GKAchievementViewControllerDelegate, MFMessageComposeViewControllerDelegate> {
BOOL gameCenterAvailable;
BOOL userAuthenticated;
UIViewController *presentingViewController;
NSMutableDictionary* earnedAchievementCache;
GKTurnBasedMatch *currentMatch;
//id <GCTurnBasedMatchHelperDelegate> delegate;
}
Here are the functions
- (void) submitAchievement: (NSString*) identifier percentComplete: (double) percentComplete
{
//GameCenter check for duplicate achievements when the achievement is submitted, but if you only want to report
// new achievements to the user, then you need to check if it's been earned
// before you submit. Otherwise you'll end up with a race condition between loadAchievementsWithCompletionHandler
// and reportAchievementWithCompletionHandler. To avoid this, we fetch the current achievement list once,
// then cache it and keep it updated with any new achievements.
if(self.earnedAchievementCache == NULL)
{
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error)
{
if(error == NULL)
{
NSMutableDictionary* tempCache = [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement* score in scores)
{
[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];
}];
}
}
}
- (void) achievementSubmitted: (GKAchievement*) ach error:(NSError*) error;
{
if((error == NULL) && (ach != NULL))
{
if (ach.percentComplete == 100.0) {
//UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Achievement Earned!"
// message:(#"%#",ach.identifier)
// delegate:nil
// cancelButtonTitle:#"OK"
// otherButtonTitles:nil];
//[alert show];
//[alert release];
TRACE("achievement submitted %s\n", [ach.identifier UTF8String]);
}
}
else
{
// Achievement Submission Failed.
printf("Achievement Submission Failed\n");
}
}

Looks like this is the missing code you are looking for. Just put it inside of your GCTurnBasedMatchHelper.m file.
- (void) callDelegateOnMainThread: (SEL) selector withArg: (id) arg error: (NSError*) err
{
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self callDelegate: selector withArg: arg error: err];
});
}
- (void) callDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err
{
assert([NSThread isMainThread]);
if([delegate respondsToSelector: selector])
{
if(arg != NULL)
{
[delegate performSelector: selector withObject: arg withObject: err];
}
else
{
[delegate performSelector: selector withObject: err];
}
}
else
{
NSLog(#"Missed Method");
}
}

Just add the method which doesn't exist.

Related

Comparing 2 cities from 2 locations in background

I have an app that the user saves a location and set the radius to be notified about it: 1KM, 5KM (the user will recive a notification if the location he saved earlier and he's current location in in the setted radius) and "City" (the user will recive a notification if the city of the location he saved earlier and the city where he currently is are the same city).
Note: I'm using Google Maps iOS SDK.
I have a method called -sendNotificationIfNeeded that checks if to send a notification, code:
-(void)sendNotificationIfNeeded
{
if (self.muteEnabled==YES) { //Check if the user muted the app
NSLog(#"Mute enabled");
return;
}
//Important part:
if([self checkIfUserInRadiusForLocation:[self.savedLocations objectAtIndex:0]])
{
UILocalNotification *notification=[self createNotification];
[self sendNotification:notification];
}
}
The method checkIfUserInRadiusForLocation check the radius of the savedLocation (CLCircularRegion) and checks if the user is inside this radius, code:
-(BOOL)checkIfUserInRadiusForLocation:(SavedLocation*)location
{
if(location.radiusString isEqualToString:#“1KM”)
{
if(location.circularRegion containsCoordinate:self.locationManager.location.coordinate)
{
return YES;
}
}else if(location.radiusString isEqualToString:#“5KM”)
{
if(location.circularRegion containsCoordinate:self.locationManager.location.coordinate)
{
return YES;
}
//Important part:
}else if(location.radiusString isEqualToString:#“City”)
{
if([self checkIfUserCityIsSameToLocation:location)
{
return YES;
}
}
return NO;
}
The method checkIfUserCityIsSameToLocation: finds the savedLocation city and the user's location city and checks if they're equals (the problem is here), code:
-(BOOL)checkIfUserCityIsSameToLocation:(savedLocation*)location
{
//Checking user's city
__block NSString *userCity;
[[GMSGeocoder geocoder]reverseGeocodeCoordinate:self.locationManager.location.coordinate completionHandler:^(GMSReverseGeocodeResponse *response, NSError *error) {
if (error) {
NSLog(#"%#",[error description]);
}
userCity=[[[response results] firstObject] locality];
}];
//Checking savedLocation’s city
__block NSString *savedLocationCity;
[[GMSGeocoder geocoder]reverseGeocodeCoordinate:location.circularRegion.center completionHandler:^(GMSReverseGeocodeResponse *response, NSError *error) {
if (error) {
NSLog(#"%#",[error description]);
}
savedLocationCity=[[[response results] firstObject] locality];
NSLog(#"");
}];
if ([userCity isEqualToString:savedLocationCity]) { //Both string cities are nil
Return YES;
}else
{
Return No
}
}
The problem is that when the app reaching the if statment on checkIfUserCityIsSameToLocation: both userCity and savedLocationCity are nil (becuase reverseGeocodeCoordinate: completionHandler: method is running on a background thread).
I can't figure out how to solve it,
I'll really appriciate if someone will help me with this.
Thank you very much!
Do it like this:
-(BOOL)checkIfUserInRadiusForLocation:(SavedLocation*)location userCity: (NSString *) userCity locationCity: (NSString *) locationCity
{
if(location.radiusString isEqualToString:#“1KM”)
{
if(location.circularRegion containsCoordinate:self.locationManager.location.coordinate)
{
return YES;
}
}else if(location.radiusString isEqualToString:#“5KM”)
{
if(location.circularRegion containsCoordinate:self.locationManager.location.coordinate)
{
return YES;
}
//Important part:
}else if(location.radiusString isEqualToString:#“City”)
{
if([userCity isEqual: locationCity])
{
return YES;
}
}
return NO;
}
-(void)sendNotificationIfNeeded
{
if (self.muteEnabled==YES) { //Check if the user muted the app
NSLog(#"Mute enabled");
return;
}
SavedLocation* location = [self.savedLocations objectAtIndex:0];
[[GMSGeocoder geocoder]reverseGeocodeCoordinate:self.locationManager.location.coordinate completionHandler:^(GMSReverseGeocodeResponse *response, NSError *error) {
if (error) {
NSLog(#"%#",[error description]);
}
NSString *userCity=[[[response results] firstObject] locality];
[[GMSGeocoder geocoder]reverseGeocodeCoordinate:location.circularRegion.center completionHandler:^(GMSReverseGeocodeResponse *response, NSError *error) {
if (error) {
NSLog(#"%#",[error description]);
}
NSString *savedLocationCity=[[[response results] firstObject] locality];
if([self checkIfUserInRadiusForLocation:location userCity: userCity locationCity: savedLocationCity])
{
UILocalNotification *notification=[self createNotification];
[self sendNotification:notification];
}
}];
}];
}

How to retrieve Game center's achievement percentage and submit back with modification?

I have already configured all Game Center functions and below code i am using to unlock achievement, which is perfectly working fine.
- (void) unlockAchievementThis:(NSString*)achievementID {
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:
achievementID];
if (achievement){
achievement.percentComplete = 100;
achievement.showsCompletionBanner = true;
[GKAchievement reportAchievements:#[achievement] withCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error at unlockAchievementThis()");
}
}];
}
}
Now My problem is with incremental achievements. I have another method for few achievements and I want the previous achievement percentage to increase it with a constant.
My game is in cpp and i don't know much ObjC.
I got some code below which i think should help me but i don't know how to use achievementDescriptions to get percentage and add incStep into it and submit it to back game center.
- (void) incrementAchievementThis:(NSString*)achievementID :(NSInteger) incStep
{
NSMutableDictionary *achievementDescriptions = [[NSMutableDictionary alloc] init];
[GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) {
if (error != nil) {
NSLog(#"Error getting achievement descriptions: %#", error);
}
for (GKAchievementDescription *achievementDescription in descriptions) {
[achievementDescriptions setObject:achievementDescription forKey:achievementDescription.identifier];
}
}];
Percentages are stored in GKAchievement percentComplete, so you need to load (and update and report) GKAchievements instead of GKAchievementDescriptions.
GKAchievmenentDescriptions are configured in iTunes Connect and are "read-only" from the point of view of your app.
Finally i got output by below code...
- (void) incrementAchievementThis:(NSString*)achievementID :(NSInteger) incStep
{
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error)
{
if (error == nil) {
for (GKAchievement* achievement in achievements) {
if ([achievementID isEqualToString:achievement.identifier]) {
achievement.percentComplete += incStep;
[GKAchievement reportAchievements:#[achievement] withCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error at incrementAchievementThis()");
}
}];
}
}
}
else {
NSLog(#"Error in loading achievements: %#", error);
}
}];
}

when the ALAssetsLibraryGroupsEnumerationResultsBlock function is trigger in ios

In my app i want to get the image and video from particular album.so i use ALAssetsLibraryGroupsEnumerationResultsBlock function for get the images and video from the particular Album.here i store the asset value in a nsmutablearray,i get that array values on viewdidLoad, but in viewdidload the array value is null.So when ALAssetsLibraryGroupsEnumerationResultsBlock is trigger.
Below is my code help me..
- (void)viewDidLoad {
[super viewDidLoad];
if (self.assetLibrary == nil) {
_assetLibrary = [[ALAssetsLibrary alloc] init];
}
if (self.groups == nil) {
_groups = [[NSMutableArray alloc] init];
} else {
[self.groups removeAllObjects];
}
[self loadAssetImages];
}
-(void)loadAssetImages{
NSLog(#"Load asset Image ");
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error) {
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:#"ERROR" message:[NSString stringWithFormat:#"No Albums Available"] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
};
ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) {
NSLog(#"Block");
if (group == nil)
{
return;
}
if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:#"FlipDemo"]) {
[self.groups addObject:group];
[self loadImages];
return ;
}
if (stop) {
return;
}
};
NSLog(#"Block Out");
[self.assetLibrary enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock: listGroupBlock
failureBlock:failureBlock];
}
-(void)loadImages{
NSLog(#"Load Image000");
ALAssetsGroup *assetGroup;
for (assetGroup in self.groups)
{
for (int i = 0; i<[self.groups count]; i++)
{
[ assetGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if (result) {
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo])
{
[videoOnly addObject:result];
}
else if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto])
{
[imgOnly addObject:result];
// NSLog(#"Photo");
}
}
}
];
}
}
}
The method is asynchronous. You can find the function detail in help document:
help document
(void)enumerateGroupsWithTypes:(ALAssetsGroupType)types
usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock
failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
This method is asynchronous. When groups are enumerated, the user may be asked to confirm the application's access to the data; the method, though, returns immediately. You should perform whatever work you want with the assets in enumerationBlock.
end of help document
So, according your code, you can't get anything in your main thread. Because iOS get assets in another thread.
You can set a key-value observation to make main thread waiting for this asynchronous function.
1) set a property, for example: handleOverFindFlag
2) before execute the function: set handleOverFindFlag=NO
3) add Observer
[self addObserver:self forKeyPath:#"handleOverFindFlag" options:0 context:NULL];
4) set observing function:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:
(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#""])
{
//handle your findings
}
}
5) after doing this
[self removeObserver:self forKeyPath:#"handleOverFindFlag"];
You can find detail about Key-Value coding in help document.

Game Center Score Submission

In GCHelper.h
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
#interface abGCHelper : NSObject {
BOOL gameCenterAvailable;
BOOL userAuthenticated;
}
#property (assign, readonly) BOOL gameCenterAvailable;
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier;
#end
In GCHelper.m
#import "abGCHelper.h"
#implementation abGCHelper
#synthesize gameCenterAvailable;
static abGCHelper *_sharedHelper = nil;
-(abGCHelper*)defaultHelper {
// dispatch_once will ensure that the method is only called once (thread-safe)
static dispatch_once_t pred = 0;
dispatch_once(&pred, ^{
_sharedHelper = [[abGCHelper alloc] init];
});
return _sharedHelper;
}
- (BOOL)isGameCenterAvailable {
// check for presence of GKLocalPlayer API
Class gcClass = (NSClassFromString(#"GKLocalPlayer"));
// check if the device is running iOS 4.1 or later
NSString *reqSysVer = #"4.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer
options:NSNumericSearch] != NSOrderedAscending);
return (gcClass && osVersionSupported);
}
- (id)init {
if ((self = [super init])) {
gameCenterAvailable = [self isGameCenterAvailable];
if (gameCenterAvailable) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(authenticationChanged) name:GKPlayerAuthenticationDidChangeNotificationName object:nil];
}
}
return self;
}
- (void)authenticateLocalUserOnViewController:(UIViewController*)viewController
setCallbackObject:(id)obj
withPauseSelector:(SEL)selector
{
if (!gameCenterAvailable) return;
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
NSLog(#"Authenticating local user...");
if (localPlayer.authenticated == NO) {
[localPlayer setAuthenticateHandler:^(UIViewController* authViewController, NSError *error) {
if (authViewController != nil) {
if (obj) {
[obj performSelector:selector withObject:nil afterDelay:0];
}
[viewController presentViewController:authViewController animated:YES completion:^ {
}];
} else if (error != nil) {
// process error
}
}];
}
else {
NSLog(#"Already authenticated!");
}
}
-(void)authenticationChanged {
if ([GKLocalPlayer localPlayer].isAuthenticated && !userAuthenticated) {
NSLog(#"Authentication changed: player authenticated.");
userAuthenticated = TRUE;
// Load the leaderboard info
// Load the achievements
} else if (![GKLocalPlayer localPlayer].isAuthenticated && userAuthenticated) {
NSLog(#"Authentication changed: player not authenticated.");
userAuthenticated = FALSE;
}
}
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier
{
GKScore *scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier: identifier];
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!");
}
}];
}
I referred to online tutorials to do this. Now, the problem is:
How do I submit the highScore defined in MyScene.m ?
Where do I have to put my Leaderboard ID?
Please help
Thanks...

Validating emails with firebase for iOS

When submitting a request to create a new user, if the user tries to submit with an invalid email, the app crashes (I.E. without an # sign). Is there a way to check if an email is valid through Firebase or is that something I need to validate app side?
The error I get in the console is:
* Terminating app due to uncaught exception 'InvalidEmail', reason: 'jfkdsla;fjdk is an invalid email address'
NSString *url = [NSString stringWithFormat:#"https://myapp.firebaseio.com/users/%#", displayName.text];
Firebase *dataRef = [[Firebase alloc] initWithUrl:url];
[dataRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"Ref %#", snapshot.value);
snapshotValue = snapshot.value;
}];
if (snapshotValue == NULL) {
AppDelegate *appDelegate= (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.authClient createUserWithEmail:email.text password:password.text andCompletionBlock:^(NSError* error, FAUser*user) {
if (error != nil) {
// Error
NSLog(#"ERROR CODE: %d",error.code);
NSLog(#"ERROR: %#",error);
if (error.code == -9999) {
email.textColor = [UIColor redColor];
}
} else {
NSLog(#"Created %#",user.userId);
Firebase *userRef = [[Firebase alloc] initWithUrl:[NSString stringWithFormat:#"https://myapp.firebaseio.com/users/%#",displayName.text]];
[[userRef childByAppendingPath:#"name"] setValue:fullName.text];
[[userRef childByAppendingPath:#"user_id"] setValue:user.userId];
Firebase *userCredentials = [[Firebase alloc] initWithUrl:[NSString stringWithFormat:#"https://myapp.firebaseio.com/users/%#/credentials",displayName.text]];
[[userCredentials childByAppendingPath:#"email"] setValue:email.text];
[[userCredentials childByAppendingPath:#"password"] setValue:password.text];
AppDelegate *appDelegate= (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.authClient loginWithEmail:email.text andPassword:password.text
withCompletionBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
NSLog(#"There was an error logging in to this account: %#",error);
} else {
NSLog(#"logged in");
[self dismissViewControllerAnimated:NO completion:nil];
[appDelegate.window.rootViewController presentViewController:appDelegate.tabBarController animated:YES completion:nil];
}
}];
}
}];
}
else {
displayName.textColor = [UIColor redColor];
}
}
Since an exception is being thrown, throw the "createUserWithEmail" line of code into a "#try{} / #catch{}" block:
#try {
[appDelegate.authClient createUserWithEmail:email.text password:password.text andCompletionBlock:^(NSError* error, FAUser*user) {
...
...
// keep that big huge block part intact
...
...
}
}
#catch (NSException * e) {
NSLog(#"Exception: %#", e);
}
}

Resources