I am completely confused as to why this leaks. After xCode 7.2 was released, the program I was working on started having memory leaks. I've stripped all of the code to the bare minimum and the leak is still here. If anyone knows how to instantiate a GameScene class with an initializer and avoid leaks please let me know.
Here is the GameViewController:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size: CGSize(width: view.bounds.size.width, height: view.bounds.size.height))
let skView = view as! SKView
skView.ignoresSiblingOrder = true
skView.showsNodeCount = true
skView.showsFPS = true
skView.showsPhysics = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
// the rest is standard
Here is the GameScene:
import SpriteKit
class GameScene: SKScene {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(size: CGSize) {
super.init(size: size)
}
override func didMoveToView(view: SKView) {
}
}
Here is what instruments is telling me
This tells me that I am creating GameScene in a way that causes some reference look perhaps, but surely there is a way to create a GameScene and have an initializer in place.
Remove this line:
skView.showsPhysics = true
Related
Explanation
My question is pretty straight forward: how do I print a variable from GameScene on GameViewController?
Code
I created this code below so it's easier to get the idea.
GameScene.swift
import SpriteKit
class GameScene: SKScene {
var variable = Int()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
variable = 50
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
NSNotificationCenter.defaultCenter().postNotificationName("calledFromGameSceneVC", object: nil)
}
}
GameViewController.swift
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Set view size.
let scene = GameScene(size: view.bounds.size)
// Configure the view.
let skView = 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 = .ResizeFill
skView.presentScene(scene)
//------------------------------//
//Register observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.calledFromGameScene), name: "calledFromGameSceneVC", object: nil)
}
func calledFromGameScene(){
//Print variable
let scene = GameScene(size: view.bounds.size)
print("My variable from GameScene is: ", scene.variable)
}
}
Sorry for the brief explanation.
Thanks in advance,
Luiz.
The type of inter-object communication you are trying to achieve is probably best addressed through a delegation pattern using a protocol:
GameScene.swift
import SpriteKit
protocol GameSceneDelegate {
func calledFromGameScene(scene: GameScene)
}
class GameScene: SKScene {
var variable = Int()
var gameDelegate: GameSceneDelegate?
override func didMoveToView(view: SKView) {
/* Setup your scene here */
variable = 50
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
gameDelegate?.calledFromGameScene(self)
}
}
GameViewController.swift
class GameViewController: UIViewController, GameSceneDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Set view size.
let scene = GameScene(size: view.bounds.size)
// Configure the view.
let skView = 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 = .ResizeFill
/* Set the delegate */
scene.gameDelegate = self
skView.presentScene(scene)
}
func calledFromGameScene(scene:GameScene){
//Print variable
print("My variable from GameScene is: ", scene.variable)
}
}
Note that you can't use the property name delegate on your GameScene as SKScene already has a delegate property
I am new to swift and trying add some extra features after reading this tutorial http://www.raywenderlich.com/66877/how-to-make-a-game-like-candy-crush-part-1. What I want to do is combine with the element of digital pets, kind like the iOS game 'Best Friend'. So first, I add a main scene(picture of main scene) and several buttons, when button is pressed, the scene would change. Below are the codes I used to achieve it.
MainSceneController.swift:
import UIKit
import SpriteKit
class MainSceneController: UIViewController {
#IBOutlet weak var settingButton: UIButton!
#IBOutlet weak var storeButton: UIButton!
#IBOutlet weak var petsButton: UIButton!
#IBOutlet weak var levelButton: UIButton!
override func prefersStatusBarHidden() -> Bool {
return true
}
override func shouldAutorotate() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
let mainScene = MainScene(size:CGSize(width: 320, height: 568))
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
mainScene.scaleMode = .AspectFill
skView.presentScene(mainScene)
}
#IBAction func changeScene(sender: UIButton) {
let labelText = sender.titleLabel?.text!
print(labelText)
switch (labelText!) {
case "LevelButton":
let scene = LevelScene(size:CGSize(width: 320, height: 568))
let reveal = SKTransition.fadeWithDuration(1)
scene.scaleMode = SKSceneScaleMode.AspectFill
scene.view?.presentScene(scene, transition: reveal)
case "PetsButton":
let scene = PetsScene(size:CGSize(width: 320, height: 568))
let reveal = SKTransition.fadeWithDuration(1)
scene.scaleMode = SKSceneScaleMode.AspectFill
scene.view?.presentScene(scene, transition: reveal)
default:
break
}
}
}
import SpriteKit
class PetsScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.redColor()
let mylabel = SKLabelNode(fontNamed: "Chalkduster")
mylabel.text = "PetsScene"
mylabel.fontSize = 70
mylabel.position = CGPoint(x: self.frame.size.width / 2,
y: self.frame.size.height / 2)
self.addChild(mylabel)
}
}
When I pressed the button, the scene do not change. I also use segue to achieve it. But is seems like not what I want. And I have looked for some projects source code, but I cannot find similar one. I would appreciate any advice, thanks in advance.
I am not sure if this is what you are looking for either, but it is the method I use to transition between scenes. I do not use Segues. I am not sure why, other than that I never bothered to learn how to use them well enough to default to them. I only rarely use Storyboards, even. So the below code is an example of how to transition from scene to another just in code. I usually have a GlobalFunctions.swift file that holds functions that are shared by many classes, the following would be in such a file:
func transitionToScene(node: String, sendingScene: SKScene) {
//println("Entering transition to scene method with \(node) and \(sendingScene.name)")
let transDur = 1.5
let transition = SKTransition.fadeWithColor(sendingScene.backgroundColor, duration: transDur)
var scene = SKScene()
switch node {
case splashSceneString:
scene = SplashScene(size: sendingScene.size)
case gameSceneString:
scene = GameScene(size: sendingScene.size)
case optionsSceneString:
scene = OptionsScene(size: sendingScene.size)
case menuSceneString:
scene = MenuScene(size: sendingScene.size)
default:
if general_debug == true { print("Default should not be reached. No Scene?") }
}
scene.scaleMode = SKSceneScaleMode.AspectFill
sendingScene.view!.presentScene(scene, transition: transition)
}
So this func would be called by some event. For example if you touched a button within your game/app the touchesEnded: function would do something like this:
transitionToScene("optionsSceneString", sendingScene: self.scene)
Hope this helps somewhat. Good luck!
I am very new to Swift and iOS development. I was watching tutorials on iOS development w/ Swift and SpriteKit. Following the tutorials I opened Xcode, new project, game, universal; and all I changed was the GameScene.swift. Here is the new code:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var node1 = SKNode()
node1.position = CGPoint(x: 100, y: 100)
self.addChild(node1)
var spr1 = SKSpriteNode(imageNamed: "Spaceship")
spr1.position = CGPointZero
spr1.zPosition = 1
node1.addChild(spr1)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Note: the Spaceship image is provided by default.
So in the tutorials, this code added a spaceship to the scene. However, when I run the simulator, the scene remains blank. What can be the problem? If more info is needed, please say so and I will provide.
It could be running correctly, and you just can't see it on the screen. Depending on the device that you're emulating, you may be showing the spaceship off screen. Try a different, smaller device, to emulate, and check if you can see it.
Try going into your GameViewController.swift and making sure that you see something along the lines of this:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
}
I am trying to initialize a scene in swift like so: let scene = GameScene(size: skView.bounds.size)
However the following error pops up in xcode:
Cannot invoke initializer for type 'GameScene' with an argument list of type '(size: CGSize)
Here is the related piece of code:
override func viewWillLayoutSubviews() {
var skView: SKView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
let scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
You probably might want to include the following initializer within your GameScene
override init(size: CGSize) {
super.init(size: size)
}
I've ported over this project - https://gist.github.com/cmezak/8393434 and have it working but I have a question about the differences in handling instances of a class and also initializing properly.
In GameScene.swift I have created a property with an instance to an SKSpriteNode like so:
class GameScene: SKScene {
var nodeToScroll: SKSpriteNode
init(size: CGSize) {
nodeToScroll = SKSpriteNode(imageNamed: "image")
super.init(size: size)
nodeToScroll.anchorPoint = CGPointMake(0, 0)
nodeToScroll.position = CGPointMake(0, size.height - nodeToScroll.size.height)
self.addChild(nodeToScroll)
}
}
And in GameViewController.swift I have:
var skView = SKView()
var scene = GameScene(size: CGSize())
var scrollView = UIScrollView()
class GameViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
skView = SKView(frame: CGRectMake(0, 0, 1024, 768))
self.view.addView(skView)
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scrollView = UIScrollView(frame: skView.frame)
scrollView.hidden = true
scrollView.delegate = self
self.view.addSubview(scrollView)
scrollView.contentSize = scene.calculateAccumulatedFrame().size
skView.addGestureRecognizer(scrollView.panGestureRecognizer)
skView.presentScene(scene)
}
Like I said, this is working but my attempts to follow Swift patterns such as
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
it breaks. So my question is, how am I supposed to be handling cases where I need an instance of a scene outside of viewDidLoad? I thought this would be a simple port but now I've been staring at it so long I don't even know what I'm doing. Suggestions?
Edit:
I edited the viewController to this:
var skView: SKView!
var scene: GameScene!
var scrollView: UIScrollView!
class GameViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// 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
self.view.addSubview(skView)
/* Set the scale mode to scale to fit the window */
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
scrollView = UIScrollView(frame: skView.frame)
scrollView.hidden = true
scrollView.delegate = self
self.view.addSubview(scrollView)
scrollView.contentSize = scene.calculateAccumulatedFrame().size
skView.addGestureRecognizer(scrollView.panGestureRecognizer)
skView.presentScene(scene)
}
}
I don't get any warnings or errors but when I run it I get "fatal error: use of unimplemented initializer 'init(coder:)' for class 'SwiftScrollKit.GameScene'"
So then I added
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
to the GameScene class and now get 'Can't add self as subview' -- hooboy... I don't know why this is throwing me for such a loop but I just must not be grasping something here that is probably right under my nose.
You're going to be stuck until you get the scope of your instance variables corrected. In the gist you linked to, they're declared inside the #implementation { ... } block of your ViewController, but here they're declared outside the class completely, which means they're global in scope.
Inside viewDidLoad, you're redefining scene and skView in the scope of that method, hiding the global values while you do your configuration. These local variables then get deallocated at the end of viewDidLoad, so you've lost access to whatever you've set up there. (They may live on in the view hierarchy, but your global variables won't work.)
You'll want to move those global variables inside the class, and then access them using self.variableName so you don't mix them up with local variables. Other methods in the class will then also have access to what you've set up in viewDidLoad:
class GameViewController: UIViewController, UIScrollViewDelegate {
var skView: SKView!
var scene: GameScene!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
self.scene = // load the scene
self.skView = SKView(frame: CGRectMake(0.0, 0.0, 1024.0, 768.0))
self.view.addSubview(self.skView)
// etc
}
}
Finally, the "Can't add self as subview" error is happening because of this:
let skView = self.view as SKView
// ...
self.view.addSubview(skView)