0
votes

I have a simple (multi scene) spritekit game and would like to implement and display a settings screen using a UITableViewContoller. I am close but keep losing my reference to the games view controller when i change scene, which means i can only segue to the UITableViewController on the first scene when my game starts. I'm hoping someone can help. I setup the UITableView in the storyboard and setup a segue to the tableview from my GameViewController in there.

I have the following set up in my GameViewController file:

override func viewDidLoad() {
    super.viewDidLoad()

    if let view = self.view as! SKView? {
        // Load the SKScene
        if let scene = SKScene(fileNamed: "MenuScene") {
            // Set the scale mode to scale to fit the window
            scene.scaleMode = .aspectFill

            //I'VE ADDED THIS
            if let gameScene = scene as? MenuScene {
                gameScene.viewController = self
            }
            // Present the scene
            view.presentScene(scene)
        }

        view.ignoresSiblingOrder = true
    }
}

func showSettings() {
    performSegue(withIdentifier: "openSettings", sender: self)
}

which gives me a reference to the GameViewController from my SKScene. I then load the UITableViewController from my SKScene in spritekit when a user touches an SKLabelNode. A simpler version of my MenuScene code looks something like this:

class MenuScene: SKScene {
var viewController: GameViewController?

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
    let touch:UITouch = touches.first!
    let positionInScene = touch.location(in: self)
    let touchedNode = self.atPoint(positionInScene)

    if let name = touchedNode.name
    {
        let transition:SKTransition = SKTransition.fade(withDuration: 0.75)
        var scene:SKScene = SKScene()

        if (name == "settingsBTN"){
            viewController?.showSettings()
        }
        else if (name == "randomBTN"){
            scene = StarScene(size: self.size)
            scene.backgroundColor = SKColor.black
            self.view?.presentScene(scene, transition: transition)
        }
    }
}

}//end of MenuScene

The table view is presented fine when i tap the settingsBTN when first starting the game. however, if i visit another scene in my game (e.g. by pressing randomBTN) and return back to the menu scene i can no longer segue to the Settings view as i have lost the reference to the viewcontroller (it shows as nil) so i can't call the showSettings method in the GameViewController that performs the segue (presumably because i set up the reference to the view controller in the view controller itself).

I can't figure out how to maintain the reference to my GameViewController so i can present the settings screen at any point. Please could someone help.

1

1 Answers

1
votes

There are two simple methods to call a GameViewController function from other classes: through a delegate or by making the class instance static.

// DELEGATE
//
// GameViewController.swift

protocol GameView_Delegate
{
    func show_ui()
}

class GameViewController: UIViewController, GameView_Delegate
{
    override func viewDidLoad()
    {

        //   ... 

        if let view = self.view as! SKView?
        {
            if let scene = SKScene(fileNamed: "MenuScene")
            {        
                scene.scaleMode = .aspectFill

                // pass GameViewController reference
                scene.gv_delegate = self
            }

            view.presentScene(scene)
         }

        // ...

    }

    func show_ui()
    {
        // show ui
        // ...
    }
}

// MenuScene.swift

class MenuScene: SKScene
{
    var gv_delegate : GameView_Delegate!

    func settings()
    {
        // call function show_ui from GameViewController
        gv_delegate.show_ui()
    }

    func game_scene()
    {
        let transition = SKTransition.fade(with: UIColor.white, duration: 1.0)
        let next_scene = GameScene()
        next_scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        next_scene.scaleMode = self.scaleMode
        next_scene.size = self.size

        // before moving to next scene, we must pass the delegate, so we
        // don't  lose the connection with the GameViewController
        next_scene.gv_delegate = gv_delegate
        self.view?.presentScene(next_scene, transition: transition)
    }
}

// GameScene.swift

class GameScene: SKScene
{
    var gv_delegate : GameView_Delegate!

    func settings()
    {
        // call function show_ui from GameViewController
        gv_delegate.show_ui()
    }

    func main_scene()
    {
        let transition = SKTransition.fade(with: UIColor.white, duration: 1.0)
        let next_scene = MainScene()
        next_scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        next_scene.scaleMode = self.scaleMode
        next_scene.size = self.size

        // before moving to next scene, we must pass the delegate, so we
        // don't lose the connection with the GameViewController
        next_scene.gv_delegate = gv_delegate
        self.view?.presentScene(next_scene, transition: transition)
    }
}

When you enter a new scene, for example GameScene, the old scene MainScene in our case is deallocated (all variables emptied, all references destroyed) and a new GameScene instance is created and displayed on screen. If afterwards you want to exit that scene and enter MainScene, a new instance of that class will be created with the GameViewController reference empty. We should reference it again or pass the reference before exiting the scene (as in my example).

// STATIC
//
// GameViewController.swift

class GameViewController: UIViewController
{
    static var shared : GameViewController!

    override func viewDidLoad()
    {
        super.viewDidLoad()

        GameViewController.shared = self

        // ...

    }

    func show_ui()
    {
        // show ui
        // ...
    }
}


// MenuScene.swift

class MenuScene: SKScene
{
    func settings()
    {
        // call function show_ui from GameViewController

        GameViewController.shared.show_ui()
    }
}

Read pros/cons of each method and chose the one that suits your app the best. Also, avoid using UIKit inside SpriteKit.