Adding SKScene to UIViewController gives me errors - swift

What I want
An infinite background that randomly adds objects (in my cases little stars) to the middle of the screen. Then those objects needs to move out of the screen with a certain speed. This way I want to give the user the feeling they are moving "into space", seeing stars they are passing.
What I have
A single view application with a UIViewController class.
Problem
I think the right way is to add a SKScene which will do the task I want. However, adding the SKScene class next to UIViewController gives me an error: "multiple inheritance from classes 'UIViewController' and 'SKScene'". I need to add the SKScene to an existence UIViewController, in order to call some important functions. When searching for this on Stackoverflow I came across this: SKScene in UIViewController But I can not call certain functions like "override func update(current time: CFTimeInterval){}", because the answer doesn't show how to implement the SKScene class. This answer deletes the whole UIViewController class: Adding an SKScene to a UIViewController?. The other answers are not helping me.
Question
What is the correct way of adding a SKScene class to an existence UIViewController class, so I can accomplish what I want?
Edit: Came across this on 9gag and its exactly what I want: http://9gag.com/gag/aWmZAPZ Those stars are coming from left top, the respawning should be in the middle of the screen at my app.

Certainly, it's possible. Instead of directly add SKScene to UIViewController, you need to warp SKScene by SKView first, and then add SKView to UIViewController
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Configure the view.
// Core:- convert the base view/UIView of UIViewController to SKView
let rootView = self.view as! SKView
// skView.showsFPS = true
// skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
rootView.ignoresSiblingOrder = true
rootView.frameInterval = 1
//init your SKScene
let rootMenuScene = YourScene(size: rootView.bounds.size)
rootMenuScene.scaleMode = .resizeFill
//warp in SKView
rootView.presentScene(rootMenuScene)
}
...
}

Related

MTKView delegate does not work [duplicate]

This question already has answers here:
Swift can't call protocol method via delegate
(2 answers)
Closed 4 years ago.
I am writing a simple metal app.
I tried to separate view and render. So I write a MTKViewDelegate class to to do render work and set the view's delegate to it. But it does not work. But when I make the view controller instance as the delegate, it works.
blow is my code
class ViewController: UIViewController,MTKViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! MTKView
//view.delegate = self //this works,print 'drawing'
view.delegate = ViewController() //this does not work, not print anyting
print("view did load")
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
print("change size")
}
func draw(in view: MTKView) {
print("drawing")
}
}
Why this happens?
update
What I really meant was ,why when I set current instance of ViewController as view's delegate , it worked, but when I create a new instance, it did not work.
What I really want to do is making a render class to do render work as MTKView's delegate. But it did not work, so I test it with ViewController.
Thanks for matt's answer,I knew the reason is that , the delegate property of MTKView is weak referenced, so it could not hold the new created instance of MTKViewDelegate. The new created instance is recycled by garbage colletor.
What I need was just that the delegate property of MTKViewDelegate is 'weak'.When people do not notice this, the situation is confusing.
So,I think my question is valuable, and it's not like question Swift can't call protocol method via delegate. I think I should not be blocked to ask questions again.
The problem is that this line:
view.delegate = ViewController()
...makes a second ViewController, makes it the delegate, and then throws it away.
You (the class containing this code) are the ViewController you want to be the delegate. So you need to say:
view.delegate = self
It works when you say that because self is a persistent object. The other ViewController() comes into existence and just vanishes again; it has no persistence so it can't do anything.
Why do you think it's wrong to say view.delegate = self? It's normal and it works.

watchOS 3.0 detect crown rotation in SpriteKit

So as of watchOS 3.0 you are now able to get the rotation of the digital crown. I managed to use the crownDidRotate function in an InterfaceController.
But I can't get the rotation of the crown from inside a SKScene Class.
Can anybody help me with this I'm pretty lost right now?
Thanks.
To get those crownDidRotate calls in your interface controller, you had to adopt the WKCrownDelegate protocol in your interface controller, and set your interface controller as the delegate of its crownSequencer.
To get crownDidRotate calls in some other class, adopt the WKCrownDelegate protocol in that class, and set an instance of that class as the delegate of your interface controller's crownSequencer.
Presumably you already have some code like this to set up your SpriteKit scene:
class InterfaceController: WKInterfaceController {
#IBOutlet var spriteGizmo: WKInterfaceSKScene!
override func awake(withContext context: AnyObject?) {
super.awake(withContext: context)
let scene = MyScene(fileNamed: "MyScene")
spriteGizmo.presentScene(MyScene(fileNamed: "MyScene"))
}
}
If you've declared WKCrownDelegate conformance in your MyScene class, just add a line to set it as the delegate of the interface controller's crown sequencer:
let scene = MyScene(fileNamed: "MyScene")
spriteGizmo.presentScene(MyScene(fileNamed: "MyScene"))
crownSequencer.delegate = scene
(Alternatively, you may set your WKInterfaceSKScene's scene in the Storyboard. In that case, you can still reference the WKInterfaceSKScene from your interface controller with an IBOutlet. Then in awake(withContext:), you can access the scene through that outlet and set it as the crown delegate.)
In watchOS 3 just any object object can get digital crown events by setting them as a delegate:
let crownSequencer = WKExtension.shared().rootInterfaceController!.crownSequencer
crownSequencer.delegate = self
crownSequencer.focus()
Then read back the value by implementing:
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double)
It is important to call the focus(), especially for controllers whose UI fits the screen and do not need actual scrolling.

Swift: Deallocate GameScene after transition to new scene?

So I've read several questions on this but most are in Objective-C and I haven't found any that address/answer this directly.
I am new to programming here so please explain any suggestions really thoroughly.
I need to understand how to deallocate my GameScene after game over is reached. The reason I need to do this is because after game over, I transition to a new SKScene (the game over scene) and when I transition back to my GameScene, either GameScene is not reset entirely or I'm generating a new GameScene each time.
I say this because My frames per second count goes down every time I get game over and transition back to the GameScene, so that if I do it enough times I can't even play the game anymore. This is a problem.
I don't understand why this happens because when I transition to my game over scene, I remove everything from my GameScene:
self.viewController!.performSegueWithIdentifier("goToGameOver", sender: nil)
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
I also read here: After Closing SKScene, Memory Remains High that removing the SKView that contains an SKScene deallocates the SKScene, so when both my GameScene and game over scenes are left, I remove BOTH views from their superviews like this:
override func viewDidDisappear(animated: Bool) {
print("its gone")
self.view.removeFromSuperview()
}
But nothing has any affect on the decreasing frames per second count and the game keeps lagging. I don't understand what I'm not doing and can't tell whether the game over scene is the problem or I'm just not deallocating GameScene.
How do I deallocate the GameScene? What else should I be doing?
EDIT:
My node count and draws count remain about the same every time I transition back to GameScene, so that doesn't seem to be a problem. I tried testing whether deinit was called when transitioning between scenes (I didn't have a deinit before) using this within the GameScene class:
} //<**End of GAMESCENE**
deinit {
print("Deinit was called")
}
And nothing is printed so deinit is not being called.
EDIT 2:
#alex_p Here is what I have in my GameViewController-
class GameViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
var scene1: GameScene?
var scene2: GameScene?
var skView: SKView?
// Action to present next Scene
#IBAction func nextSceneAction(sender: AnyObject) {
print("Next scene")
// Create new GameScene object
scene2 = GameScene(fileNamed:"GameScene")
// Present scene2 object that replace the scene1 object
skView!.presentScene(scene2) //ERROR ON THIS LINE
scene1 = nil
}
And I call this method from my GameScene like this:
self.viewController.nextSceneAction(self)
I copied yours exactly but I am receiving the error unexpectedly found nil while unwrapping an Optional value on the line commented on above. Deinit in not being called.
In my project, I used the following mechanism and it worked well. All my scenes SKScene objects were optional variables. When i need the new scene to show, i create it and present at SKView. When i need to display the new scene, I set the previous object scene object to nil, this immediately reducing the reference count by 1, and becouse of at this moment no one object is not use my scene, the reference count becomes zero and scene was deleted.
The SKScene object is a ordinary class object and ARC works with them like with all reference type objects. You only need to monitor the number of references to the scene. All are finished with the various resources I was start in deinit of SKScene object
The simple example:
At UIViewController we have optional objects of GameScene:
class GameViewController: UIViewController {
var scene1: GameScene?
var scene2: GameScene?
var skView: SKView?
// Action to present next Scene
#IBAction func nextSceneAction(sender: AnyObject) {
print("Next scene")
// Create new GameScene object
scene2 = GameScene(fileNamed:"GameScene")
// Present scene2 object that replace the scene1 object
skView!.presentScene(scene2)
scene = nil
}
override func viewDidLoad() {
super.viewDidLoad()
// Create GameScene object
scene = GameScene(fileNamed:"GameScene")
skView = (self.view as! SKView)
skView!.showsFPS = true
skView!.showsNodeCount = true
skView!.ignoresSiblingOrder = true
scene!.scaleMode = .AspectFill
// Present current scene
skView!.presentScene(scene)
}
}
At GameScene in deinit print some text to show that it object will be deleted:
class GameScene: SKScene {
...
deinit {
print("Deinit scene")
}
}
Debug output after push the nextSceneAction button:
Next scene
Deinit scene

How to call a function in viewController from GameScene

I am wanting to call a share function during game play. I have some code I can use in my GameViewController
func showTweetSheet() {
let tweetSheet = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
tweetSheet.completionHandler = {
result in
switch result {
case SLComposeViewControllerResult.Cancelled:
//Add code to deal with it being cancelled
break
case SLComposeViewControllerResult.Done:
//Add code here to deal with it being completed
//Remember that dimissing the view is done for you, and sending the tweet to social media is automatic too. You could use this to give in game rewards?
break
}
}
tweetSheet.setInitialText("Test Twitter") //The default text in the tweet
tweetSheet.addImage(UIImage(named: "TestImage.png")) //Add an image if you like?
tweetSheet.addURL(NSURL(string: "http://twitter.com")) //A url which takes you into safari if tapped on
self.presentViewController(tweetSheet, animated: false, completion: {
//Optional completion statement
})
}
I have also set the ViewController in my GameScene Class...
var viewController: GameViewController!
... and set the scene.viewController to self in my ViewController
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* 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
skView.presentScene(scene)
scene.viewController? = self
}
}
However, When I call the function like so...
viewController.showTweetSheet()
... from my GameScene it gives me a "found nil when unwrapping an optional value" error.
I think I may need to set the scene.viewController to self later on, but I don't know how to do that in the viewController.
Any help would be much appreciated.
First of all, you don't need the question mark after scene.viewController.
Second, scene.viewController = self should come before skView.presentScene(scene). This will probably fix your problem.
Lastly, it's considered bad design (or at least sloppy) to make an SKScene have a property that is a UIViewController. The scene class is now tied to using a UIViewController, and if you want to extend your code to something that doesn't use UIViewController to control views (e.g. you want to make a Mac version of your game), it won't work right away because it's hard-coded to work with UIViewController.
The "pure" way to do this would be a technique called "delegation" by iOS programmers. You create a protocol that will be your delegate, and make your view controller implement that protocol. Then the SKScene uses the protocol, not UIViewController.
All that said, you might want to leave out this complexity.

In swift, how to get memory back to normal after an SKScene is removed?

I created a simple game with SpriteKit, however every time I run the game, the memory usage in simulator increases about 30mb, but never decreases when the game is finished.
When I run the game over ten times the simulator gets slower and slower and eventually crashes.
In this simple game I have two controllers and a gamescene:
MainController calls GameViewController via a button triggered
In GameViewController, gamescene is initialised in this way:
class GameViewController: UIViewController
{
var skView:SKView!
var scene:GameScene!
override func viewDidLoad() {
super.viewDidLoad()
scene = GameScene(size: view.bounds.size)
skView = view as SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.viewController = self
skView.presentScene(scene)
}
//with a prepareForSegue deinitialises the scene and skview:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "GameFinished"{
scene.removeAllActions()
scene.removeAllChildren()
scene.removeFromParent()
scene = nil
skView.presentScene(nil)
skView = nil
let target = segue.destinationViewController as MainController
}
}
}
In the GameScene, viewController is a property
var viewController:GameViewController? = GameViewController()
the segue is triggered with this:
self.viewController!.performSegueWithIdentifier("GameFinished", sender: nil)
I've also tried putting remove methods into deinit in GameScene:
deinit{
self.removeAllActions()
self.removeAllChildren()
}
Still wouldn't work
Your GameViewController has a strong reference to your GameScene. And your GameScene had a strong reference to your GameViewController. This leads to a strong reference cycle, which means that neither objects will be deallocated.
You need to declare your viewController property in your GameScene as weak.
weak var viewController:GameViewController? = GameViewController()
Using Swift 3, Xcode 8 and iOS 10.
After avoiding strong references, taken care of SKTextures, etc. memory level didn't recover after dismissing the scene and returning to the "menu" viewController.
I was using:
override func sceneDidLoad() {...}
This is available in iOS 10 but I wanted iOS 8&9 compatibility that's why I've
changed to the older:
override func didMove(to view: SKView) {...}
Beside getting compatible with older iOS versions, it turns out that the memory level drops after dismissing the scene. This was a surprise. I'm missing probably some leaks but it's working for me.
I hope it helps someone.