How to reference the current Viewcontroller from a Sprite Kit Scene
Asked Answered
C

5

11

I'm having a hard time finding the answer to this question I assume is not that hard.

How can I reference methods and properties defined on a viewcontroller from a SKScene ?

And building on that: How can you reference the ViewController from a SKScene that was loaded from within another SKScene?

Cavorelievo answered 17/2, 2014 at 11:25 Comment(4)
Retrieving the current rootViewController should be enough for you, unless you need to do something specific. You can do it like this [UIApplication sharedApplication].keyWindow.rootViewController; . Other solution would be to subclass SKScene and give it presentingVC property, set this property upon creating each scene to send it around.Stout
You should avoid to reference UIViewController from SKScene, because it breaks MVC patternSachsen
Like @AndreyGordeev mentions, you should really avoid referencing you Scene's viewController directly. It should never have to know about the viewController directly. What could however make sense is to implement a delegate protocol on your SKScene-subclass and have the viewController set up to be this delegate. Perhaps you should take a step back and inform us what you are trying to achieve, rather than a particular way to implement this?Spain
I added Game Center authentication logic to my main ViewController. FYI I'm working with a single viewcontroller as this is a simple game. I keep properties on my viewcontroller that keep track of general GameCenter availability + the authenticated user. Now, when my game ends, I want to report my Scores to gamecenter. So I need to get in touch with my viewcontroller in order to check if the gamecenter functionality is available and the user is logged on. I don't want to pass the properties for this to my SKScene because I'm not sure what will happen when user gets unauthenticated during play.Cavorelievo
D
13

A better option than my "back reference" answer is to use a protocol to explicitly define the methods that a SKScene subclass expects the UIViewController subclass to implement. Here are the steps you'll need to go through.

1) Define the protocol (contract) that the UIViewController will need to adopt and conform to:

protocol GameManager{
    func loadHomeScene()
    func loadGameScene(level:Int)
    func loadHighScoreScene()
}

2) The UIViewController subclass now adopts the protocol:

class GameViewController: UIViewController,GameManager{

3) The GameScene gets an ivar that references the UIViewController:

var gameManager:GameManager?

4) When the GameManager needs to call the UIViewController, it does so like this:

gameManager.loadHighScoreScene()

We now have a well-defined contract of what a GameManager is. Any other developers (and the current developer's future self) that work on this project now know which methods a GameManager should implement. This means we could easily port this code to a different project (and thus a different UIViewController) and it would still work as long as this new UIViewController adopted the GameManager protocol.

Similarly, if we decided to move our scene navigation code out of the view controller, and instead placed it in a singleton class, this singleton class would merely need to adopt the GameManager protocol.

Which approach to use?

This thread gives 4 approaches to solving the problem, and I have personally used all 4. Here's my take on them:

1) Hard-coding a call to the UIViewController subclass:

(self.view!.window!.rootViewController as! GameViewController).loadGameScene(1)

Advantage: 1 line of code!

Disadvantage: A very tight coupling of 2 classes, without an explicit contract.

2) Using a pointer back to the UIViewController:

Advantages: Very little code required, No protocol required.

Disadvantages: Tight coupling of 2 classes. No explicit contract.

3) Using NSNotificationCenter:

Advantage: Loosely coupled classes.

Disadvantages: more code needed to set up the broadcaster, receiver, and callback methods. Harder to trace and debug the code. No explicit contract. The "one-to many" capability of notifications is nice, but doesn't help us here.

4) Creating a protocol:

Advantages: An explicit contract. The gods of MVC will be pleased.

Disadvantage: More code to write (but much less than notifications).

Dampproof answered 20/4, 2016 at 13:1 Comment(0)
S
9

You should avoid to reference UIViewController from SKScene, because it breaks MVC pattern.

As an alternative way you can use NSNotificationCenter to notify UIViewController about high score:

In ViewController:

- (void)awakeFromNib {
    [[NSNotificationCenter defaultCenter] 
        addObserver:self
        selector:@selector(reportScore:)
        name:@"ReportScore"
        object:nil];
}


-(void)reportScore:(NSNotification *) notification {
    NSDictionary *userInfo = [notification userInfo];
    NSNumber *scores = (NSNumber *)[userInfo objectForKey:@"scores"];
    // do your stuff with GameCenter....
}


- (void) dealloc
{
    // If you don't remove yourself as an observer, the Notification Center
    // will continue to try and send notification objects to the deallocated
    // object.
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

In SKScene:

- (void)gameOver {
    NSDictionary *userInfo = [NSDictionary dictionaryWithObject:
         [NSNumber numberWithInt:self.scores forKey:@"scores"];
    [[NSNotificationCenter defaultCenter]
         postNotificationName:@"ReportScore" object:self userInfo:userInfo];
}
Sachsen answered 17/2, 2014 at 12:31 Comment(1)
in SKScene, you forgot NSNumber's closing ']'. Thanks for answer by the way.Platform
M
5

in theory you should not do that but in practice...

self.view.window.rootViewController
Maible answered 10/7, 2014 at 23:31 Comment(1)
The Swift version is even uglier (self.view!.window!.rootViewController as! GameViewController).loadGameScene()Dampproof
F
3

Thanks for your NSNotification method for exiting a sprite kit scene back to the calling view controller. This is a simple way to jump back to UIKit & will post my code. I'm working is swift & just added 4 lines of code for those working in Swift. This code was added to the view controller in the viewDidLoad function.

override func viewDidLoad() {
    super.viewDidLoad()

    if let scene = GameLevels.unarchiveFromFile("GameLevels") as? GameLevels{
        // Configure the view.
        let skView = self.view as! SKView
        skView.showsFPS = true
        skView.showsNodeCount = true

        /* Sprite Kit applies additional optimizations to improve rendering performance */
        skView.ignoresSiblingOrder = true

        /* Set the scale mode to scale to fit the window */
        scene.scaleMode = .AspectFill

        //set up notification so scene can get back to this view controller
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "quitToLevel:", name: "quitToLevelID", object: nil)
        skView.presentScene(scene)
    }
}

// Function to pop this view controller and go back to my Levels screen
func quitToLevel(notification: NSNotification) {
    self.navigationController!.popViewControllerAnimated(true)
}

I added this code to my touchesBegan function in my SKscene to get back to the view controller

            else if nodeTouched.name == "quit"
        {
            NSNotificationCenter.defaultCenter().postNotificationName("quitToLevelID", object: nil)
        }

Now when I hit the "quit" labelNode, it will let me run a function called quitToLevel in the view controller that presented my scene. This function pops me back to my main screen since I'm using a navigation controller but could be used for any purpose.

I tried several other methods of exiting a scene, but had issues if I was jumping around in multiple scenes of loosing the reference back to the view controller. I can use this method from any scene

Futch answered 5/5, 2015 at 5:42 Comment(1)
great solution because it was as simple as you can get adding only a few lines. thanks.Living
D
0

This isn't the most elegant solution, but how about a "back reference" from your SKScene subclass to the GameViewController? Something like this:

// call from GameViewController
let scene = HomeScene(size:screenSize, scaleMode:SKSceneScaleMode.AspectFill, viewController: self)

Your SKScene subclass looks like this:

class HomeScene: SKScene {
    var viewController:GameViewController
    init(size:CGSize, scaleMode:SKSceneScaleMode, viewController:GameViewController) {
        self.viewController = viewController
        super.init(size:size)
        self.scaleMode = scaleMode
    }
}

and later:

func gameOver(){
    self.viewController.loadHighScoreScene()
}

SKNode and its subclasses already have a .parent and a .scene property, so it's not like this approach is unheard of. Yeah, we're hard-coding a dependency and more tightly coupling our code, but we're writing less code, so it can't be all bad.

Dampproof answered 27/2, 2016 at 2:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.