I enabled Game Center functionality in my SpriteKit game in the ViewController. Everything works fine, but I want to show the Leaderboard in another Scene after a Button is touched. I imported everything correctly. My project crashes now after touching the 'HighScoreButton' in the Head.m file, with following Output:
'NSInvalidArgumentException', reason: '-[UIView presentScene:]: unrecognized selector sent to instance 0x7feec2ff2920'
My recent Code that doesn't work:
ViewController.h
#interface ViewController : UIViewController <GKGameCenterControllerDelegate>
- (void)showLeaderboardAndAchievements:(BOOL)shouldShowLeaderboard;
+ (ViewController*)defaultHelper;
#end
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
[self authenticateLocalPlayer];
// Create and configure the scene.
SKScene * scene = [Head sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
self.canDisplayBannerAds = YES;
// Present the scene.
[skView presentScene:scene];
}
static ViewController *_sharedHelper = nil;
+ (ViewController*)defaultHelper {
// dispatch_once will ensure that the method is only called once (thread-safe)
static dispatch_once_t pred = 0;
dispatch_once(&pred, ^{
_sharedHelper = [[ViewController alloc] init];
});
return _sharedHelper;
}
-(void)showLeaderboardAndAchievements:(BOOL)shouldShowLeaderboard{
GKGameCenterViewController *gcViewController = [[GKGameCenterViewController alloc] init];
gcViewController.gameCenterDelegate = self;
if (shouldShowLeaderboard) {
gcViewController.viewState = GKGameCenterViewControllerStateLeaderboards;
gcViewController.leaderboardIdentifier = _leaderboardIdentifier;
}
else{
gcViewController.viewState = GKGameCenterViewControllerStateAchievements;
}
[self presentViewController:gcViewController animated:YES completion:nil];
}
-(void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController
{
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
}
Head.m
if ([Node.name isEqualToString:#"HighScoreButton"]){
[[ViewController defaultHelper] showLeaderboardAndAchievements:YES];
}
I believe the problem is with the singleton initialization of your view controller (which is correct btw).
When first loaded the view controller is created "behind the scenes" (I am not sure where exactly but I'll look for it) and its view is initialized with an skView instance. (And since it is not created by you your singleton instance is not initialized and a different ViewController instance is used)
Since your defaultHelper method is not used on the first creation of your view controller you are creating a new view controller which its view is an instance of UIView and it does not comply to the SKView methods (such as setShowFPS and presentScene). And of course since you create a new view controller it is not part of the view hierarchy.
What I recommend for you is to retrieve the view controller as follows :
if ([Node.name isEqualToString:#"HighScoreButton"]){
ViewController *viewController = self.view.window.rootViewController;
[viewController showLeaderboardAndAchievements:YES];
}
Related
I am not an experienced app developer, I am modifying an obj c spriteKit game. The concept that I need to implement is, when the user clicks a button in the menu scene (SKScene) "talk" to the navigation controller and show another storyboard who they gave me.
For this I use a delegate from the scene, in order to "talk" to the gameview controller's navigation controller and reference the other storyboard.
Everything works, but when from the starting scene(EndGameScene in the code) the user goes to the gamescene and back then it does not work. And there is no error message!
GameViewController.m
SKView * skView = (SKView *)self.view;
skView.ignoresSiblingOrder = YES;
_scene = [[EndGameScene alloc] initWithSize:self.view.frame.size];
_scene.scaleMode = SKSceneScaleModeAspectFill;
self.scene.delegate = self;
// Present the scene.
[skView presentScene:_scene];
...
//delegation method
-(void)showDifferentView {
UIStoryboard *storyboard = [SettingsViewController settingsStoryBoard];
SettingsViewController *controller = [storyboard instantiateViewControllerWithIdentifier:SettingsControllerStoryboardIdentifier];
[self.navigationController pushViewController:controller animated:YES];
self.navigationController.navigationBarHidden = NO;
}
GameScene.m
-(void)backToHomePage{
[self removeAllChildren];
[self removeAllActions];
[self removeFromParent];
SKScene *endGameScene = [[EndGameScene alloc] initWithSize:self.size];
endGameScene.scaleMode = SKSceneScaleModeAspectFill;
GameViewController *gameVC = [[GameViewController alloc] init];
endGameScene.delegate = gameVC; //when add that line crashes
SKTransition *reveal = [SKTransition fadeWithDuration:0];
[self.view presentScene:endGameScene transition:reveal];
}
If I don't go to the GameScene there is no problem. The problem is when the user goes to the GameScene and comes back.
I would appreciate any help
Thanks
I want to display a programmatically created ViewController in a storyboard ViewController. The ViewController of the Storyboard is of a different class as the 'programmatically' created ViewController.
I've got the following classes:
ViewController (linked to storyboard scene, and implementation happens here)
OnboardingVC (all elements are created over here)
I've tried the following:
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
self.onboardVC = [self generateFirstDemoVC]; // returns in an instance ofOnboardingViewController
self = (ViewController*)self.onboardVC;
}
return self;
}
This (obviously) crashes.
What I don't want is this:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window setRootViewController:self.onboardVC];
[self.window makeKeyAndVisible];
Because this doesn't take the setup in the storyboard into account, and I don't want that, because the VC needs to managed by a NavigationController. How do I accomplish that?
It sounds like you have a UINavigationController as the entry point to your storyboard. You can manipulate this navigation controller when the application launches, to add, remove, or replace view controllers, etc.
For example:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
UIViewController* initialVc = [OnboardingViewController generateFirstDemoVC]; // or whatever
UINavigationController* nav = (id)_window.rootViewController;
nav.viewControllers = #[ initialVc ];
return YES;
}
I am trying to display an admob interstitial ad after receiving a successful ad call :
- (void)interstitialDidReceiveAd:(GADInterstitial *)ad {
DLog(#"interstitialDidReceiveAd");
[ad presentFromRootViewController:self];
}
self is the ViewController instance (which is the root view controller as implemented by the SpriteKit template) and for some reason it displays the ad (which is good) but restarts the entire scene.
I should note that the ad request is done way after the scene is created and being run.
I also noticed this behavior when I tried adding a view to this controller's view property (which is a SpriteKit SKView instance)
And ideas ?
As I thought... I was doing something wrong...
If you stumble upon this problem :
I create my scene in the view controller's method viewWillLayoutSubviews (until this method the view bounds are NOT updated with the desired orientation)
When the interstitial ad is presented this method is called again and of course the scene is recreated as well. So just add a boolean member indicating if the scene was already loaded for creating the scene only once.
Poof, problem is gone:)
There's something funky going on here that's more than just displaying an interstitial. I have a sprite kit project setup where I display the UIImagePickerController from the rootViewController via:
#pragma mark - ImageCatureDelegate methods
-(void)requestImagePicker
{
UIImagePickerController *imagePicker = [UIImagePickerController new];
imagePicker.delegate = self;
[self presentViewController:imagePicker animated:YES completion:nil];
}
#pragma mark - UIImagePickerControllerDelegate
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
[picker dismissViewControllerAnimated:YES completion:^{
SKTexture *imageTexture = [SKTexture textureWithImage:image];
CIFilter *sepia = [CIFilter filterWithName:#"CISepiaTone"];
[sepia setValue:#(0.8) forKey:kCIInputIntensityKey];
imageTexture = [imageTexture textureByApplyingCIFilter:sepia];
SKView *view = (SKView *)self.view;
MyScene *currentScene = (MyScene *)view.scene;
[currentScene setPhotoTexture:imageTexture];
}];
}
during the callback, nothing special is done except dismissing the modal. If admob is pushing a new VC you may see this issue, but if it's displaying a literal modal VC modally... then you shouldn't see this issue. You may want to look into exactly what happens when you push an interstitial ad. If this is the case, and this is the only method you can use, You'll have to NSCode or something to keep the game's state so it can be loaded back up when the interstitial disappears.
Edit:
To append to your solution above, if you use viewWillLayoutSubviews to correct the portrait/landscape issue in SpriteKit, be sure to do it as such so this only happens 'once'
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
if (!skView.scene) {
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
MyScene * scene = [[MyScene alloc] initWithSize:skView.bounds.size andLevelNumber:1];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
}
I have an app that is not working properly with state restoration. It previously did, but as I started moving away from the storyboard it stopped.
My app starts with a LoginViewController that is the starting view controller in my storyboard. If the login is successful, then it tries to add two FolderViewController to a navigation controller. This is so that the visible folder is one level deep already. This is done in the following code:
UINavigationController *foldersController = [[UINavigationController alloc] initWithNavigationBarClass:nil toolbarClass:nil];
foldersController.restorationIdentifier = #"FolderNavigationController";
FolderViewController *root = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
root.folderId = 0;
FolderViewController *fvc = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
fvc.folderId = 1;
[foldersController setViewControllers:#[root, fvc] animated:YES];
[self presentViewController:foldersController animated:YES completion:nil];
The FolderViewController has this awakeFromNib
- (void)awakeFromNib
{
[super awakeFromNib];
self.restorationClass = [self class]; // If we don't have this, then viewControllerWithRestorationIdentifierPath won't be called.
}
Within the storyboard the FolderViewController has a restorationIdentifier set. When I press the Home button, the app is suspended. My restoration calls in FolderViewController are being called:
// This is being called
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt64:self.folderId forKey:#"folderId"];
}
The problem now is when I try and restore. I stop the app in my debugger and then start it up again. This kicks off the restoration process.
First, my viewControllerWithRestorationIdentifierPath:coder: for my LoginViewController is called. This doesn't do much, and its use is optional. I've tried removing it and I don't have any ill effect.
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
LoginViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (LoginViewController *)[sb instantiateViewControllerWithIdentifier:#"LoginViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [LoginViewController class];
}
return vc;
}
Next, the viewControllerWithRestorationIdentifierPath:coder: for my FolderViewController is called:
// This is being called
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
FolderViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (FolderViewController *)[sb instantiateViewControllerWithIdentifier:#"FolderView"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [FolderViewController class];
vc.folderId = [coder decodeInt32ForKey:#"folderId"];
}
return vc;
}
I've previously had a decodeRestorableStateWithCoder: as well, and it did get called. However, since it's setup in the viewControllerWithRestorationIdentifierPath:coder:, it wasn't necessary to keep it around.
All of these things are being called the appropriate number of times. But in the end, the only view controller that is displayed in the LoginViewController. Why are my FolderViewControllers not being displayed. Is there a missing setup that I need to do in my LoginViewController to attach the view controllers that I manually added previously?
Edit
After reading http://aplus.rs/2013/state-restoration-for-modal-view-controllers/ which seemed relevant, I added the following code to the App delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
if ([identifierComponents.lastObject isEqualToString:#"FolderNavigationController"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"FolderNavigationController";
return nc;
}
else
return nil;
}
I think the App is happier now, but it still isn't restoring properly. Now I get this error in my log:
Warning: Attempt to present <UINavigationController: 0xbaacf50> on <LoginViewController: 0xbaa1260> whose view is not in the window hierarchy!
It's something different.
I had similar issue. My stack of view controllers was pretty simple: root view with table view -> some details view -> some edit details view. No navigation views, views are modal. And it did not work.
Turned out, the issue was that, viewControllerWithRestorationIdentifierPath() in the root view controller should look like this:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSDictionary *myRestorableObj = [coder decodeObjectForKey:#"myRestorableObj"];
// #todo Add more sanity checks for internal structures of # myRestorableObj here
if (myRestorableObj == nil)
return nil;
return [[UIApplication sharedApplication] delegate].window.rootViewController;
}
Instantiating new root view controller is wrong. It creates new stack of view controllers that the stored children view controllers down the stack do not belong to.
All the other children view controller should be created as usually, with the following code:
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"MyChildViewController"];
Hope this helps.
You need to assign different restoration identifiers to different FolderViewController objects.
For example:
FolderViewController *folderViewController1 = // initialize object ;
FolderViewController *folderViewController2 = // initialize object ;
folderViewController1. restorationIdentifier = #"folderViewController1";
folderViewController2. restorationIdentifier = #"folderViewController2";
I tried the code above and it worked fine.
I used the code below to show the Leaderboard but all i got it the console this
cocos2d: surface size: 480x320
the code:
- (void)showLeaderboardForCategory:(NSString *)category
{
// Only execute if OS supports Game Center & player is logged in
if (hasGameCenter)
{
// Create leaderboard view w/ default Game Center style
GKLeaderboardViewController *leaderboardController = [[GKLeaderboardViewController alloc] init];
// If view controller was successfully created...
if (leaderboardController != nil)
{
// Leaderboard config
leaderboardController.leaderboardDelegate = self; // The leaderboard view controller will send messages to this object
leaderboardController.category = category; // Set category here
leaderboardController.timeScope = GKLeaderboardTimeScopeAllTime; // GKLeaderboardTimeScopeToday, GKLeaderboardTimeScopeWeek, GKLeaderboardTimeScopeAllTime
// Create an additional UIViewController to attach the GKLeaderboardViewController to
myViewController = [[UIViewController alloc] init];
// Add the temporary UIViewController to the main OpenGL view
[[[CCDirector sharedDirector] openGLView] addSubview:myViewController.view];
// Tell UIViewController to present the leaderboard
[myViewController presentModalViewController:leaderboardController animated:YES];
}
}
}
Finally I called the code like this :
[[GameCenterManager sharedGameCenterManager] showLeaderboardForCategory:#"LeaderBoard"];
I found the solution I must write :
[[[[CCDirector sharedDirector] openGLView] window] addSubview:myViewController.view];
instead of :
[[[CCDirector sharedDirector] openGLView] addSubview:myViewController.view];