I have a problem with the integration of game center in my app' which use iOS 6 SDK.
In fact I use the sample code from Apple, but it looks like incomplete :
I have tried this code :
-(void) authenticateLocalPlayer {
GKLocalPlayer* localPlayer =
[GKLocalPlayer localPlayer];
localPlayer.authenticateHandler =
^(UIViewController *loginVC,
NSError *error) {
[self setLastError:error];
if ([GKLocalPlayer localPlayer].authenticated)
{
// authentication successful
[self enableGameCenterForPlayer:[GKLocalPlayer localPlayer]];
}
else if (loginVC)
{
// player not logged in yet, present the vc
[self pauseGame];
[self presentLoginVC:loginVC];
}
else
{
// authentication failed, provide graceful fallback
[self disableGameCenter];
}
};
}
But the problem is that enableGameCenterForPlayer, pauseGame, presentLoginVC, disableGameCenter are not implemented methods, and it returns :
Instance method '-enableGameCenterForPlayer:' not found (return type defaults to 'id')
How can I fix this problem ?
Thanks
I use the method [self presentLoginVC:VC] to pass my UITabViewController or UINavigationController the viewController because the block below is not on the main thread.
localPlayer.authenticateHandler = ^(UIViewController *loginVC, NSError *error) {
When you are in a block you should be sure not to change UI elements because you really don't know when it will complete or where you will be in your app. There are probably many ways to do this, but this is my solution.
Below is my UITabBarController 'category' .m file (additions of methods for a class without subclassing) I create the method presentLoginVC and just have it call 'showGameCenterViewController' through my UITabBarController:
#import "UITabBarController+GameKitAdditions.h"
#implementation UITabBarController (GameKitAdditions)
-(void) showGameCenterViewController: (UIViewController *)VC {
[self presentViewController:VC animated:NO completion:nil];
}
-(void)dismissGameCenterViewController:(UIViewController *)VC {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
As to the other functions:
-(void) enableGameCenterForPlayer:(GKLocalPlayer *) localPlayer;
-(void) disableGameCenter;
-(void) pauseGame;
They could be as simple as just setting a BOOL called enableGameCenter to YES or NO. To get around errors you can just add these prototypes to your .h file and then write the functions just to output something to NSLog() or something.
Related
I createed a game using this tutorial. When I call authenticateLocalPlayer like so:
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler =
^(UIViewController *viewController, NSError *error) {
[self setLastError:error];
if(viewController != nil) {
[self setAuthenticationViewController:viewController];
} else if([GKLocalPlayer localPlayer].isAuthenticated) {
_enableGameCenter = YES;
} else {
_enableGameCenter = NO;
}
};
I get an error:
-[GKUnauthenticatedPlayerInternal name]: unrecognized selector sent to instance 0x14517e00
but when I add [NSThread sleepForTimeInterval:1] like this:
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
[NSThread sleepForTimeInterval:1];
localPlayer.authenticateHandler =
^(UIViewController *viewController, NSError *error) {
[self setLastError:error];
if(viewController != nil) {
[self setAuthenticationViewController:viewController];
} else if([GKLocalPlayer localPlayer].isAuthenticated) {
_enableGameCenter = YES;
} else {
_enableGameCenter = NO;
}
};
It starts working fine. Is this the correct way to solve the error?
I have the very same problem. And a random sleep doesn't solve it for me.
I find it strange that I can't find any other reference to this problem on the internet. We can not be the only people having this problem, can we?
I found out that what triggered the crash for me was a call to
[self performSelector: #selector(showLabel) withObject: nil afterDelay: 5],
where self is a SKSpriteNode. Note that it was not the moment when the selector was called but at the very call to performSelector, that I got the crash. Also note that the call had nothing at all to do with GKLocalPlayer or Game Center at all.
What solved the problem was to use SKAction instead, like this:
[self runAction:
[SKAction sequence: #[[SKAction waitForDuration:5],
[SKAction performSelector: #selector(showLabel) onTarget: self]]]];
I can't really explain why this does the trick. It seems to be a not too obvious bug with iOS 8.1, since this has never been a problem before.
It seems like the trick is to not call to initiate Game Center from viewDidLoad. If you do call it from there, call it after a delay. Agreed that this is broken in iOS 8.1 and 8.1.1 because it was never an issue before.
It appears that this is an issue with iOS 8.0-8.2. It appears to be resolved in iOS 8.3 in my experimentation. Strangely enough, my other game's code base seems to work consistently throughout (I haven't done exhaustive testing though).
I use cocos2D for iphone.
I have 3 scenes: scene A,scene B, and scene C
in scene C, click to open scene A, but scene A is not opened yet;
open scene B first, scene B has many buttons
users click a button on scene B, then scene B dismisses and returns a value v to scene C;
according to value v, scene C decides to open or not open scene A;
All I need is a callback function, I use delegate to accomplish this successfully.
I have a protocol:
#protocol SceneBDelegate <NSObject>
#required
- (void)dismissWithValue:(BOOL)value;
#end
In scene B, when a button is clicked:
-(void) clickBtn{
if([self.delegate respondsToSelector:#selector(dismissWithValue:)]){
[self.delegate dismissWithValue: YES];
}
[self removeFromParent];
}
In scene C,
-(void) dismissWithValue:(BOOL)value{
if(value){
// do something;
}
}
These codes work well, but I want to know how to accomplish this with block?
I have readed about Jens Ayton's post is this question,
how-to-perform-callbacks-in-objective-c
He explains how to use block, but I can't figure out how to connect users' action with block.
I konw UIViewController has a
I found when using UIViewController has a function:
(void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion NS_AVAILABLE_IOS(5_0);
it supports block.
But I use cocos2D, I didn't find a similar funciton.
Can anyone give me some advices? Thanks a lot!
Your scene B probably has an
#property (nonatomic, weak) id<SceneBDelegate> delegate;
instead you can store a block in a property:
#property (copy)void (^dismissWithValueBlock)(BOOL); // dismissWithValueBlock is the name of the variable.
Then in your scene B, when the button is pressed you do:
-(void) clickBtn{
if(self.dismissWithBlock){
self.dismissWithBlock(YES)
}
[self removeFromParent];
}
To create the block in your other class you should do this:
__weak typeof (self) weakSelf = self;
sceneB.dismissWithBlock = ^(BOOL value)
{
typeof (self) strongSelf = self;
// .... now your code you want to execute when this block is called..
// make sure not to call self.XXX anywhere in here. You should use the strongSelf.XXX insteadd! This is for memory management purposes. You can read up on it by googling 'strongSelf in blocks ios'
}
Yo can do that as bellow
//Define Block As in SceneBDelegate.h
typedef void (^onResultBlock)(NSDictionary *DictData, NSError *error);
- (void) onDismissWithValue:(onResultBlock) callbackBlock;
//Implementaion of Block As in SceneBDelegate.m
- (void) onDismissWithValue:(onResultBlock) callbackBlock
{
NSMutableDictionary *aMutDict = [[NSMutableDictionary alloc] init];
[aMutDict setValue:#"Value" forKey:#"Some Data"];
objCallback(aMutDict, nil);
}
//Call block from scene bellow
SceneBDelegate *aObjVC = [self.storyboard instantiateViewControllerWithIdentifier:#"SceneBDelegate"];
[aObjVC onDismissWithValue:^(NSDictionary *DictData, NSError *error)
{
}];
Try using this code:
#interface YourClass : NSObject {
void (^_block)(BOOL *result);
}
-(void) yourMethod:(NSString *)param1 withAnotherParam:(NSDictionary *)param2 withBlock:(void(^)(BOOL *result))block;
#implementation YourClass
-(void) yourMethod:(NSString *)param1 withAnotherParam:(NSDictionary *)param2 withBlock:(void(^)(BOOL *result))block
{
block = [block copy];
//do some stuff
block(YES);
}
And then, when you call yourMethod from a different class, you can do:
#implementation OtherClass
-(void) otherMethod
{
//do other stuff
[yourClassInstance yourMethod:#"hello" withAnotherParam:#{#"hello": #"hello"} withBlock:(void(^)(BOOL *result)){
//do stuff in the block....
}];
}
I hope this helps.
I have an app that presents images and/or movies in sequence. The problem is, I cannot dismiss one movie player and then present another. If I try, I get the message "Warning: Attempt to present MPMoviePlayerViewController on MyViewController while a presentation is in progress!" Unlike other animated present/dismiss methods, there is no completion handler, nor a non-animated version of present/dismiss.
Here is a simplified version of my code:
-(void) play
{
[[window rootViewController] presentMoviePlayerViewControllerAnimated:player];
[[_player moviePlayer] play];
}
-(void) videoNotification:(NSNotification *) notification
{
if([notification.name isEqualToString:MPMoviePlayerPlaybackDidFinishNotification])
{
[[window rootViewController] dismissMoviePlayerViewControllerAnimated];
[_canvasManager showNextCanvas]; //this calls play on the next canvas
}
}
Any thoughts/hints on how to achieve my goal?
After looking at dismissViewControllerAnimated:completion:, I was able to create a completion handler by creating a subclass of MPMoviePlayerViewController and overriding -(void)viewDidDisappear. viewDidDisappear gets called at the end of dismissMoviePlayerViewControllerAnimated.
#interface MyMoviePlayerViewController : MPMoviePlayerViewController
{
void (^_viewDidDisappearCallback)(void);
}
- (void)setViewDidDisappearCallback:(void (^)(void))callback;
#end
#implementation MyMoviePlayerViewController
- (id) initWithContentURL:videoPath
{
self = [super initWithContentURL:videoPath];
_viewDidDisappearCallback = nil;
return self;
}
- (void)setViewDidDisappearCallback:(void (^)(void))callback
{
_viewDidDisappearCallback = [callback copy];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if(_viewDidDisappearCallback != nil)
_viewDidDisappearCallback();
}
#end
I have some source code below which will reproduce this. Calling showWithStatus and dismiss before the HUD has a chance to present itself is causing the control to not show the next time showWithStatus is called. Does anyone know any workarounds?
// Commenting these will show SVProgressHUD
[SVProgressHUD showWithStatus:#"Loading..."];
[SVProgressHUD dismiss];
// Comment above to show SVProgressHUD
[SVProgressHUD showWithStatus:#"Loading..."];
[self performSelector:#selector(dismissHUD) withObject:nil afterDelay:5.0f];
EDIT: I have some source code up here that reproduces this.
I worked around it by doing this for the second HUD display:
[self performSelector:#selector(showHud) withObject:nil afterDelay:0.1];
I have resolved the problem:
1. add property
#property (nonatomic, readonly, getter = isDismissing) BOOL dismissing;
2.add flag
- (void)dismissWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay {
_dismissing = YES;
...
void (^completionBlock)(void) = ^{
...
_dismissing = NO;
};
...
}
Rename function
- (void)showImage:(UIImage)image status:(NSString)status duration:(NSTimeInterval)duration
to with name
- (void)showImageSS:(UIImage)image status:(NSString)status duration:(NSTimeInterval)duration
3.add delay
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration
{
if(self.isDismissing){
__weak SVProgressHUD *weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * SVProgressHUDDefaultAnimationDuration),dispatch_get_main_queue(), ^{
[weakSelf showImageSS:image status:status duration:duration];
});
}else{
[self showImageSS:image status:status duration:duration];
}
}
And
- (void)showImageSS:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration
{
// this is orginal function - (void)showImage:(UIImage)image status:(NSString)status duration:(NSTimeInterval)duration code block
}
finsh Thanks Everybody
For my iPhone app I want to implement the option to upload files to Soundcloud by making use of the CocoaSoundCloudAPI. In the instructions "How to use the SoundCloud API directly" it is explained how to modally present a loginViewController:
- (void)login {
[SCSoundCloud requestAccessWithPreparedAuthorizationURLHandler:
^(NSURL *preparedURL){
SCLoginViewController *loginViewController;
loginViewController =
[SCLoginViewController loginViewControllerWithPreparedURL:preparedURL
completionHandler:^(NSError *error){
if (SC_CANCELED(error)) {
NSLog(#"Canceled!");
} else if (error) {
NSLog(#"Ooops, something went wrong: %#", [error localizedDescription]);
} else {
NSLog(#"Done!");
}
}];
[self presentModalViewController:loginViewController
animated:YES];
}];
}
Now I replaced
[self presentModalViewController:loginViewController
animated:YES];
with
[self presentViewController:loginViewController
animated:YES
completion:nil];
because the first method is deprecated in iOS 7.
But the problem is that the Soundcloud loginViewController overlaps the status bar when presented in this fashion. And since I don't want to change the Soundcloud API I do not have the option to customize the loginViewController accordingly e.g. in its - viewDidLoad method (as suggested in many other posts on Stackoverflow).
Unfortunately there is a toolbar with a button on top the loginViewController. How can I configure my loginViewController from inside my own (presenting) view controller so that it won't overlap with the status bar when presented?
As mentioned in my comment to the original question I did not find a neat solution for this problem. However I managed to implement a workaround that does the job:
The basic idea is to add the SCLoginViewController as a child view controller of another custom view controller that is not part of the Soundcloud framework and that you can customize to your needs. This is my new login method that presents the login view controller:
- (BOOL)loginToSoundcloud {
BOOL __block success = NO;
[SCSoundCloud requestAccessWithPreparedAuthorizationURLHandler:^(NSURL *preparedURL){
SCLoginViewController *loginViewController;
loginViewController =
[SCLoginViewController loginViewControllerWithPreparedURL:preparedURL
completionHandler:^(NSError *error){
if (SC_CANCELED(error)) {
NSLog(#"Canceled!");
} else if (error) {
NSLog(#"Ooops, something went wrong: %#", [error localizedDescription]);
} else {
NSLog(#"Done!");
success = YES;
}
}];
/* BEGIN workaround for iOS7 bug:
when modally presenting a view controller it overlaps the status bar */
CBContainerVCToFixStatusBarOverlap *containerVC = [[CBContainerVCToFixStatusBarOverlap alloc] init];
[containerVC addChildViewController:loginViewController];
containerVC.view.backgroundColor = [UIColor clearColor];
if ([CBAppDelegate iOSVersionIs7OrHigher]) {
loginViewController.view.frame =
CGRectMake(loginViewController.view.frame.origin.x,
loginViewController.view.frame.origin.y + 20,
containerVC.view.frame.size.width,
containerVC.view.frame.size.height - 20);
} else {
loginViewController.view.frame =
CGRectMake(loginViewController.view.frame.origin.x,
loginViewController.view.frame.origin.y,
containerVC.view.frame.size.width,
containerVC.view.frame.size.height);
}
[containerVC.view addSubview:loginViewController.view];
/* END workaround for iOS7 bug */
[self presentViewController:containerVC
animated:YES
completion:nil];
}];
return success;
}
To check for the iOS version I implemented the following method in my CBAppDelegate:
+ (BOOL)iOSVersionIs7OrHigher {
return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1;
}
CBContainerVCToFixStatusBarOverlap is a simple view controller class with no additional methods and only one declared property. This is the content of CBContainerVCToFixStatusBarOverlap.h:
#interface CBContainerVCToFixStatusBarOverlap : UIViewController
#property (strong, nonatomic) IBOutlet UIView *containerView;
#end