1

I have a file of SKScene which is named "MainScene.swift", and created a button on it for users to jump to another page. The another page was created not by SpriteKit but with UIKit. How can I indicate the target page and write codes in this case?

I usually do like this when jump to the other scene, if I created both pages with SpriteKit.

override func viewDidLoad() {
    super.viewDidLoad()

    let scene = MainScene(size: CGSize(width: 750, height: 1334))
    let skView = self.view as! SKView

    scene.scaleMode = .AspectFit
    skView.presentScene(scene)
}

And when I do the same thing on a scene which is created with UIKit, type a Storyboard ID on the Identity column, then code like this; I only know the way using storyboard.

class ViewController: UIViewController {
    @IBAction func gotoNewPage(sender: AnyObject) {
        let nextVC = self.storyboard?.instantiateViewControllerWithIdentifier("newPage")
    presentViewController(nextVC!, animated: false, completion: nil)    
    }
}

But when try same thing between the scene which is created SKScene and UIKit, I've no idea how to specify the latter from the former scene; I'm not sticking to use the storyboard. If there's any simple way, please let me know. Thank you in advance.

vanagar
  • 3,511
  • 2
  • 10
  • 8

1 Answers1

1

This sounds like a job for the delegate pattern. Add a protocol for LevelScene;

// LevelScene.swift
protocol LevelScene : class {
    var gameDelegate: GameDelegate? { get set }
}

Add a protocol for GameDelegate;

// GameDelegate.swift
protocol GameDelegate : class {
    func gameOver()
}

In your mainScene add the protocol reference and create a property called gameDelegate;

class MainScene: SKScene, LevelScene {
    weak var gameDelegate: GameDelegate?
}

In your GameViewController add the protocol reference and implement the required protocol function - in this case it's called gameOver and seque to your UIKit View as usual;

class GameViewController: UIViewController, GameDelegate {
    func gameOver() {
        self.performSegue(withIdentifier: ExitGameSegueKey, sender: self)
    }
}

Set the delegate to gameViewController when presenting the scene;

scene.gameDelegate = self

Then in mainScene call the delegate function when required;

self.gameDelegate?.gameOver()
Mark Brownsword
  • 2,287
  • 2
  • 14
  • 23
  • To avoid strong reference cycles in your game, it's better to declare the type of the protocol as class (protocol GameScene: class { ), same thing for delegate : weak var delegate: GameScene? – Alessandro Ornano Sep 19 '16 at 12:19
  • Thank you very much for your advice Mark! However, since I first come up with protocol & delegate, I'm having a difficulty understanding what you wrote. Let me ask several things. I know I'm asking very basic things, but please be patient. 1. Where should write "protocol GameScene {...}"? In MainScene.swift? 2. If I do so, I got following error: "Invalid redeclaration of 'GameScene'". Can I just change the name something else? 3. I also got an error: "Use of undeclared type 'GameDelegate"". The error maybe clear if I create a property gameDelegate, as you say. How should I write that? – vanagar Sep 20 '16 at 04:18
  • @vanagar I've updated the example to make it clearer and hopefully answered your questions. Apple's [Swift documentation](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html) has a chapter on protocols that is worth reading. Though protocols and the delegate pattern might be an advanced topic, I think it would nicely solve your problem. – Mark Brownsword Sep 20 '16 at 11:08
  • 1
    @AlessandroOrnano Thanks for the comment, I updated the example to include the weak reference. – Mark Brownsword Sep 20 '16 at 11:14
  • @vanagar For the error `Invalid redeclaration of 'GameScene'` yes, just Rename the protocol to something else. Though ideally your class names should match the file name, so mainScene.swift would contain MainScene class. – Mark Brownsword Sep 20 '16 at 11:58
  • @MarkBrownsword You're welcome, please add also ": class" to the protocols, actually they are always both strong references – Alessandro Ornano Sep 20 '16 at 14:23
  • Thank you, Alessandro and Mark. I still trying hard to understand your advices. I'm still having several errors, and I'm afraid that the naming of my files might confused you. I had two files when I post my question. One is "MainScene.swift" and the other is "GameScene.swift". As you know, GameScene.swift is the file when you create a project with SpriteKit. I'm using it as a part of my project, but not to do with this question. In this case, should I still write "protocol GameScene {...}" on GameScene.swift? – vanagar Sep 24 '16 at 04:28
  • @vanagar You would need to give a new name to the `protocol GameScene {...}` and the file, so it doesn't conflict with the existing `GameScene.swift` – Mark Brownsword Sep 24 '16 at 04:54
  • @MarkBrownsword The reason I've asked about GameScene.swift again was I wanted make it clear before I ask next question. Actually, I had renamed the new GameScene.swift as "GameScene2.swift" to avoid the confliction as you advised me before. But if I do so, I'll have another error (Type 'MainScene' does not conform to protocol 'GameScene2') on MainScene.swift when I typed as "class MainScene: SKScene, GameScene2 {...}". What should I do to avoid this error? – vanagar Sep 24 '16 at 13:42
  • @vanagar Usually the error `Type 'MainScene' does not conform to protocol 'GameScene2'` would mean you haven't implemented the protocol members, so did you add `weak var gameDelegate: GameDelegate?` as a declaration to MainScene? – Mark Brownsword Sep 24 '16 at 22:21
  • @vanagar I updated the example to use the name `LevelScene` instead, should be less confusing for others too. – Mark Brownsword Sep 24 '16 at 22:27
  • @vanagar I think the problem was the `?` was missing from the protocol so in `LevelScene` see there is now a `?` after `GameDelegate`. Sorry for the confusion! – Mark Brownsword Sep 24 '16 at 22:52
  • @MarkBrownsword Thank you! When I added `?` after `GameDelegate` in `LevelScene.swift`, the error has gone. – vanagar Sep 25 '16 at 02:23
  • @vanagar Awesome! A protocol is a contract that has to be implemented correctly. Please accept the answer, if it has solved your problem. – Mark Brownsword Sep 25 '16 at 02:59
  • @MarkBrownsword Sorry, I'm still struggling to implement the next step. as for `func gameOver() {...}` how should I write? I just can replace `ExitGameSegueKey` with the storyboard ID(newPage) which I named? When I do so I got following error: `Value of type 'GameViewController' has no member 'performSegue'`. I've tried change the part as `self.performSeguewithIdentifier("newPage", sender: self)`, but just got similar error. Sorry, it's like I'm asking every details of each step. – vanagar Sep 25 '16 at 03:37
  • @vanagar `self.performSegue(withIdentifier: "newPage", sender: self)` should be the correct syntax to use, or you can try your original code. – Mark Brownsword Sep 25 '16 at 05:20
  • @vanagar I just realised you probably don't have a seque defined. This [Stack Overflow Post](http://stackoverflow.com/questions/26456989/how-do-i-create-a-segue-that-can-be-called-from-a-button-that-is-created-program) shows how to create a seque that can be called programmatically. Remember to give it an identifier like "newPageSeque". – Mark Brownsword Sep 25 '16 at 10:13
  • @MarkBrownsword Thank you. I've created a segue by clicking and dragging with cntl key from Game View Controller to the View Controller. Then clicked & selected the segue arrow, and typed as `newPageSeque` on the (Storyboard Segue) Identifier column. Next, I came backed to 'GameViewController.swift` and modified `func gameOver()` as `self.performSegue(withIdentifier: "newPage", sender: self)`, but the error won't change. I guess it because I didn't type `scene.gameDelegate = self` (or `self.gameDelegate?.gameOver()`) in the proper place. – vanagar Sep 26 '16 at 00:46
  • @vanagar you'll need to use newPageSeque as the identifier so `self.performSegue(withIdentifier: "newPageSeque", sender: self)` – Mark Brownsword Sep 26 '16 at 01:15
  • I'm a bit puzzled by the error `Value of type 'GameViewController' has no member 'performSeque'`. If you type `self.p` do you see any functions related to seque? – Mark Brownsword Sep 26 '16 at 03:03
  • I figured out the problem. I should have noticed it earlier. You had typed as `self.performSeguewithIdentifier`, but the 'w' of with should be upper case. When I modified the code as `self.performSegueWithIdentifier("newPageSeque", sender: self)`, the error had disappeared. (Somehow, when I changed it as `self.performSegue(WithIdentifier: "newPage", sender: self)`, I got the same error again.) Sorry for bothered you. – vanagar Sep 26 '16 at 04:18
  • Glad to hear you've figured it out! – Mark Brownsword Sep 26 '16 at 05:00
  • Let me ask you again. Seems I don't understand how to implement `scene.gameDelegate = self` and `self.gameDelegate?.gameOver()`. I've typed `scene.gameDelegate = self` at the next line of `let skView = self.view as! SKView`, and in the `MainScene.swift` I've replaced existing code on a button with `self.gameDelegate?.gameOver()`. – vanagar Sep 27 '16 at 03:54
  • When I run and tapped the button, I got following error. `Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'newPageSegue''` Sorry to keep nagging. I'm trying to understand, but it's bit difficult for me. – vanagar Sep 27 '16 at 03:54
  • You'll need to add `newPageSeque` as the seque identifier on your storyboard. – Mark Brownsword Sep 27 '16 at 04:33
  • It sounds like you have put `scene.gameDelegate = self` and `self.gameDelegate?.gameOver()` in the right place. Would be a good time to set breakpoints and step through the code to see how the delegate pattern works. Then check that you have correctly set the `newPageSeque` in the storyboard. As an alternate approach, you could use your original code with `instantiateViewControllerWithIdentifier` to navigate, just put it in the `gameOver()` function. Though I think it's better to use the storyboard seque if you can. – Mark Brownsword Sep 27 '16 at 08:59
  • I feel like @vanagar, I can't visually (yet) "see" how this works, and what is going on. It's something that could be drawn, visually, and explained that way, but I don't yet know how the relationships look, and where/what the pathways of communication and interaction are. – Confused Oct 14 '16 at 01:39
  • @Confused You can visualise it as child Node reaching up to parent Node and saying `execute this` with the constraint that the child only knows a little about the parent i.e. what is in the `protocol` – Mark Brownsword Oct 14 '16 at 02:25