I have a UITabBarController that has two items, the first one being a calendar which is just a UIWebView. The second item brings in a SpriteKit game scene and hides the tab bar (The game needs to be full screen).
I have an SKSpriteNode image that when tapped, I'd like the view to change back to the calendar view (the first tab bar item). I've searched for a way to do this and have tried various suggestions I've found here on StackOverflow, but nothing is working. The app crashes with the error
"Unexpectedly found nil while unwrapping an optional"
I understand what that error means, just not why I'm getting it. Here is the relevant code (part of which I got from here while trying to figure this out):
In GameScene.swift:
weak var viewController: GameViewController!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch:UITouch = touches.first! as UITouch
let positionInScene = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name {
if name == "exit" {
self.viewController.gameOver()
self.removeFromParent()
self.view?.presentScene(nil)
}
}
}
In GameViewController.swift:
var currentGame: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.tabBar.hidden = true
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.viewController = self
skView.presentScene(scene)
currentGame = scene
}
}
func gameOver() {
performSegueWithIdentifier("backToMain", sender: nil)
}
This is the line giving the error:
self.viewController.gameOver()
Your code is correct; I think the likely problem is that you are presenting more than one scene without transferring the value of viewController. To track this problem down, use the Find navigator (Cmd+3) to search for presentScene then ensure each call is after you have transferred the value of the viewController property across to the new scene.
I think just remove the 'weak' from the declaration of viewController. You can set a breakpoint on the line giving the error and see if viewController has a value (try po self.viewController in the Xcode console).
Related
Simply put, I want to go from a spritekit Scene to a view in the Main Storyboard. It's easy to go from the main storyboard to a spritekit scene in swift. But I can't figure out how to go back to the storyboard. Thanks for the help. Cheers.
Initial viewController: an empty viewController with a button to present the GameViewController
GameViewController: the typical GameViewController of the "Hello World" Sprite-kit template. (This is a simplified version of the two scripts as of course you will have more code in yours, however, for the purpose of sharing what I did, this is easier)
My Goal: I wanted to present the first viewController from my SKScene game with the correct deallocation of my scene.
Description: To obtain the result I've extended the SKSceneDelegate class to build a custom protocol/delegate that make the transition from the GameViewController to the first initial controller (main menu). This method could be extended to other viewControllers of your game. This delegate is made use of in the return to main menu function. Make sure to put this function before you call the class for your spritekit script.
The two scripts are shown below. Hope this helps anybody else who had my question.
UIViewController:
import UIKit
import SpriteKit
class GameViewController: UIViewController,TransitionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
scene.delegate = self as TransitionDelegate
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func returnToMainMenu(){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
guard let storyboard = appDelegate.window?.rootViewController?.storyboard else { return }
if let vc = storyboard.instantiateInitialViewController() {
print("go to main menu")
self.present(vc, animated: true, completion: nil)
}
}
}
Game Script:
import SpriteKit
protocol TransitionDelegate: SKSceneDelegate {
func returnToMainMenu()
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.run(SKAction.wait(forDuration: 2),completion:{[unowned self] in
guard let delegate = self.delegate else { return }
self.view?.presentScene(nil)
(delegate as! TransitionDelegate).returnToMainMenu()
})
}
deinit {
print("\n THE SCENE \((type(of: self))) WAS REMOVED FROM MEMORY (DEINIT) \n")
}
}
I created the GameScene first and it works, now I'm trying to add a MainMenu scene which also works except when I hit the button(sprite) to start game, then I get the error above.
The GameViewController is just stock apple code and it starts the app with the MainMenu scene, but I've tested the app just running GameScene in GameViewController and I don't get any error but then i'll have no mainMenu
Within GameScene:
var UpperLeft = SKSpriteNode()
var BottomRight = SKSpriteNode()
var UpperRight = SKSpriteNode()
var BottomLeft = SKSpriteNode()
var Ball = SKSpriteNode()
the error is with each one i'm forcing to unwrap ↓
override func didMove(to view: SKView) {
scoreLabel = self.childNode(withName:"scoreLabel") as! SKLabelNode
UpperLeft = self.childNode(withName:"UpperLeft") as! SKSpriteNode
BottomRight = self.childNode(withName:"BottomRight") as! SKSpriteNode
UpperRight = self.childNode(withName:"UpperRight") as! SKSpriteNode
BottomLeft = self.childNode(withName:"BottomLeft") as! SKSpriteNode
Ball = self.childNode(withName:"Ball") as! SKSpriteNode
The error is with each one i'm forcing to unwrap ↑
Within MainMenu:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let location = touches.first?.location(in: self) {
let touchedNode = atPoint(location)
if touchedNode.name == "StartGame" {
let transition = SKTransition.reveal(with: .down, duration: 1.0)
let nextScene = GameScene(size: scene!.size)
nextScene.scaleMode = .aspectFill
scene?.view?.presentScene(nextScene, transition: transition)
}
}
}
Within GameViewController:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "mainMenu") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
view.ignoresSiblingOrder = true
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
I understand that the error comes from the as! forcing the unwrap but I don't see how calling the GameScene from the mainMenu scene causes this to be an issue? Because I did create an object for each unwrap so they should not be nil?
The problem is in the way you initialize GameScene
use the following code:
let nextScene = GameScene(fileNamed: "GameScene")
instead of the one used in MainMenu:
let nextScene = GameScene(size: scene!.size)
when you use size to initialize a scene, it doesn't recognize the .sks file of the scene, so all your nodes which defines in .sks file become nil and that's why you get error while unwrapping them.
I'm following a tutorial to create a "tetris" game in Swift with XCode 7. I followed every single step in this tutorial, but I'm getting a runtime error:
Could not cast value of type 'SCNView' (0x106c19778) to 'SKView' (0x1068fcad0).
My GameViewController.swift is as follows:
import UIKit
import SceneKit
import SpriteKit
class GameViewController: UIViewController {
var scene: GameScene!
var swiftris:Swiftris!
override func viewDidLoad() {
super.viewDidLoad()
//Configure the view
let skView = view as! SKView
skView.multipleTouchEnabled = false
//Create and configure the scene
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scene.tick = didTick
swiftris = Swiftris()
swiftris.beginGame()
//Presente the scene.
skView.presentScene(scene)
scene.addPreviewShpaeToScene(swiftris.nextShape!){
self.swiftris.nextShape?.moveTo(StartingColumn, row: StartingRow)
self.scene.movePreviewShape(self.swiftris.nextShape!){
let nextShapes = self.swiftris.newShape()
self.scene.startTicking()
self.scene.addPreviewShpaeToScene(nextShapes.nextShape!) {}
}
}
}
override func prefersStatusBarHidden() -> Bool {
return true
}
func didTick(){
swiftris.fallingShape?.lowerShapeByOneRow()
scene.redrawShape(swiftris.fallingShape!, completion: {})
}
}
I already search for it on google and here and I didn`t find anything related to SCNView and SKView.
Thank you in advance.
I did find the problem with my code. I instantiate the SKView as the view inside GameViewController:
self.view = SKView()
let skView = view as! SKView
Before I do that, I tried to change the class of the view to SKView in the storyboard, but it was not possible.
I'd like to thank you for the revisions.
I created a new Xcode Game project. I stripped the default animations in the project and added a new Swift file and a new .sks both called MenuScene.
In my GameViewController, I replaced references to by GameScene with MenuScene as I want this scene to launch first:
override func viewDidLoad() {
super.viewDidLoad()
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "MenuScene") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! MenuScene? {
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
When I try to launch I get the following error:
Could not cast value of type 'SKScene' (0x1105624) to 'Spritekit10.MenuScene' (0x104c38).
If I change it back to GameScene, which appears to have the exact same setup, all seems fine.
Can anyone point me in the right direction with what the problem is with this?
On my MenuScene.sks under Custom Class, I've added MenuScene thinking that was the problem but the error still shows (and the textfield seems to randomly clear itself when I go back to it.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func sceneDidLoad() {
}
}
.
import SpriteKit
import GameplayKit
class MenuScene: SKScene {
override func sceneDidLoad() {
}
}
So I figured out the issue.
For the .sks file, I had to add a name to the scene in the attributes inspector (even though it was blank in the GameScene). Once I done this, the custom class value saved and the file now loads without an error. Not sure if thats a bug or by design.
Your problem is you are trying to upcast an SKScene to a MenuScene.
This cannot be done, I see you have checked your custom class in your sks file to ensure that it is set to MenuScene, but did you make sure you saved it correctly? You need to type it in and hit enter, if you do not, then the scene will not auto save it, and when you leave will clear it out.
For Xcode 8.0/8.1 , create a swift file named "Menuscene" and an sks file named "Menuscene" too.
delete everything in the Menuscene swift file and insert this code
import SpriteKit
import GameplayKit
class Menuscene: SKScene {
override func didMove(to view: SKView) {
let menutext = SKLabelNode()
menutext.text = "MEnuscene"
menutext.position = CGPoint(x: 0, y: 0)
menutext.fontSize = 120
self.backgroundColor = UIColor.blue
addChild(menutext)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let gamescene = SKScene(fileNamed: "GameScene")
gamescene?.scaleMode = .fill
self.scene?.view?.presentScene(gamescene!,transition: SKTransition.doorsCloseHorizontal(withDuration: 1.0))
}
override func update(_ currentTime: TimeInterval) {
}
}
and in the attribute inspector tab, set name to "Menuscene", hit enter (very important). then on the next tab, set custom class name to "Menuscene",hit enter (very important).
In your Gamescene.swift file, double-check to be sure your class starts with
class GameScene: SKScene {
add this code to your touchesBegan func
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let Menuscene = SKScene(fileNamed: "Menuscene")
Menuscene?.scaleMode = .fill
self.scene?.view?.presentScene(Menuscene!,transition: SKTransition.doorsOpenVertical(withDuration: 1.0))
}
finally in your Gameviewcontroller file, delete everything and insert this code
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "Menuscene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
you're welcome! you should be able to tap from game screen to menu screen.
I found something weird.
My project name has a "-" character.
After I removed this character from Target name, the project could cast scene.rootNode to GameScene.
It works on iOS 10.3.1 for me.
override func viewDidLoad() {
super.viewDidLoad()
let fileName = "GameScene"
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
guard let scene = GKScene(fileNamed: fileName) else {
return
}
guard let sceneNode = scene.rootNode as? GameScene ?? GameScene(fileNamed: fileName) else {
return
}
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
In my game I would like there to be an Google Banner View in the main menu scene and game over scene. Here's what I have in the GameViewController:
override func viewWillLayoutSubviews(){
super.viewWillLayoutSubviews()
let skView = self.view as! SKView
googleBannerView = GADBannerView(adSize: kGADAdSizeSmartBannerPortrait)
googleBannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
googleBannerView.rootViewController = self
var request: GADRequest = GADRequest()
googleBannerView.loadRequest(request)
googleBannerView.frame = CGRectMake(0, skView.bounds.height - googleBannerView.frame.size.height, googleBannerView.frame.size.width, googleBannerView.frame.size.height)
self.view.addSubview(googleBannerView!)
if skView.scene == nil{
let mainMenuScene = MainMenuScene(size: skView.bounds.size)
mainMenuScene.scaleMode = SKSceneScaleMode.AspectFill
mainMenuScene.backgroundColor = UIColor.whiteColor()
skView.presentScene(mainMenuScene)
}
}
func showBanner(){
if googleBannerView != nil{
self.googleBannerView!.hidden = false
var request: GADRequest = GADRequest()
self.googleBannerView.loadRequest(request)
}
}
func hideBanner(){
println("hideBanner() called")
self.googleBannerView.hidden = true
}
In the GameScene I have this:
override init(size: CGSize) {
super.init(size: size)
let gameViewController = GameViewController()
gameViewController.hideBanner()
When I run this it starts up fine, but when I press play it crashes and says: fatal error: unexpectedly found nil while unwrapping an Optional value. This doesn't make much sense because it can't be nil because I know there is an ad banner, right? What I am doing wrong. Thank you in advance.
-Vinny
There could possibly be an error in your show banner function. Try taking out the exclamation in:
self.googleBannerView!.hidden = false
If the banner view is not nil, then it doesn't need to unwrapped