SKScene stays in memory after dismissing its parent view controller - ios

When testing my app I realised that going to the view controller which presents my scene and back again increases the used space in memory. So I run instruments to learn what wasn’t deallocated. I found that there was some instances of my scene taking up space. How can I fix it?
I have a property in my scene class to access its view controller (which I defined as weak to avoid a retain cycle)
The concerning lines in my scene class are:
weak var viewController: MyViewControllerClass?
func exitFunction () {
viewController.dismiss(animated: true , completion: nil)
self.view!.presentScene(nil)
}

All your scenes should belong to the same view controller, that is to say in a sprite kit game there is only one view controller.
You should define your scenes and the SKView to present them on in this view controller like so:
class GameViewController: UIViewController {
var skView: SKView?
var mainMenu: MainMenu?
override func viewDidLoad() {
super.viewDidLoad()
skView = self.view as! SKView?
}
Inside your scene make sure you are deallocating your resources properly:
override func willMove(from view: SKView) {
removeAllChildren()
}
deinit {
//Deallocate your resources here
}
WillMove is called each time you move from the scene to another.
Finally when you show your scenes from inside your viewController class make sure you deallocate old ones first:
func showMainMenu(_ notification: Notification) {
deallocScenes()
mainMenu = MainMenu(fileNamed: "MainMenu")
mainMenu?.scaleMode = .fill
skView!.presentScene(mainMenu)
}
func deallocScenes(){
mainMenu = nil
levelSelection = nil
credits = nil
}

Related

Reusing 'expensive' UIViewController

I am embedding an AVPlayerViewController(which is expensive in terms of resources) in a UIViewController (using containment). I need to use another AVPlayerViewController in the subsequently pushed view controller in navigation stack, but it would be nice if I can remove it from the parent & embed it in the child. When the child pops, I want to embed it back in the parent. What is the elegant way to do this (code or storyboards)?
You need to do it in code. Create your 'expensive' view controller and store it using strong reference somewhere. You can show it programmatically anywhere, then you can dismiss it, but it will be store by strong reference. Later you can show it again.
P.S. looks like you view controller have AVPlayer, in this case probably you need to add some method to 'wipe' its state before reuse or at leave pause playback
It can be done like this way. You need to setup player .
import UIKit
import AVKit
class AVViewController: UIViewController {
static var player : AVPlayerViewController?
#IBOutlet var containerView: UIView!
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.addChildViewController(AVViewController.player!)
self.containerView.addSubview((AVViewController.player?.view)!)
// setup player here.
}
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
for vc in self.childViewControllers{
if let vc = vc as? AVPlayerViewController, AVViewController.player == nil {
AVViewController.player = vc
}
}
// Do any additional setup after loading the view.
}
}

Switching from a one ViewController to another

I know this question has been asked many times, however most aren't properly answered and do not follow the same set up as mine.
I have two UIViewControllers, GameViewController and MenuViewController. The MenuViewController uses a storyboard and UIKit. The GameViewController loads an SKScene. When the app starts it opens the MenuViewController. I have already made a button that segues to the Game on the Menu in the storybaord. That works, but now I am trying to do the opposite and return back to the Menu while in the Game. I have tried to use code that has answered similar questions but none seems to work.
Here is my GameViewController that I messed around with:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView?{
if let scene = SKScene(fileNamed: "GameScene"){
let gameScene = scene as! GameScene
gameScene.gameViewControllerDelegate = self
gameScene.scaleMode = .aspectFill
view.presentScene(gameScene)
}
view.showsFPS = true
view.showsNodeCount = true
view.ignoresSiblingOrder = true
// Do any additional setup aft)er loading the view.
}
}
func callMethod(inputProperty:String){
print("inputProperty is: ",inputProperty)
}
And the code I added to my GameScene:
class GameScene: SKScene, SKPhysicsContactDelegate {
weak var gameViewControllerDelegate:GameViewControllerDelegate?
override func didMove(to view: SKView) {
gameViewControllerDelegate?.callMethod(inputProperty: "call game view controller method")

Transition from SKScene to UIViewcontroller

For example if i have:
class SpriteKitScene: SKScene {
...
}
And in there i want to have an image, that when tapped(pressed, clicked, touched whatever)
loads another file with:
class UiViewcontrollerScene: UIViewcontroller {
...
}
I know how to transition from SKScene to SKScene, but i need to transition from SKScene to UIViewcontroller.
First, set yourself up a delegate using a protocol for your view controller.
protocol UIViewControllerDelegate{
}
See here: https://makeapppie.com/2014/07/01/swift-swift-using-segues-and-delegates-in-navigation-controllers-part-1-the-template/ for a nice tutorial on how to do that
Create an SKView class that will be hosting this delegate
class GameView : SKView
{
var delegate : UIViewControllerDelegate?
}
Then on your viewDidLoad in your UIViewController class, assign the delegate to your view controller.
override func viewDidLoad()
{
if let view = self.view as? GameView
{
view.delegate = self
}
}
Now your view has a delegate to your view controller, From this point, in your protocol file, make a method to transition
E.G.
protocol UIViewControllerDelegate
{
optional func transitionToMenuVC()
}
Then apply the code to your view controller class.
class ViewController : UIViewController, UIViewControllerDelegate
{
...
func transitionToMenuVC()
{
// do transition code here
}
}
Now you have it all set up for your view to communicate with your view controller.
In your Scene, you would just cast the scene's view to the GameView, and use the delegate to transition
class GameScene : SKScene
{
...
func transition()
{
if let view = self.view as? GameView
{
view.delegate.transitionToMenuVC()
}
}
}
Do note however, it is impossible to transition from scene to view controller, because they are 2 different animals. You will be transitioning the views, and are therefor stuck using the animations provided for views.

(Swift2 SpriteKit) How to transition between an SKScene and a UIViewController efficiently?

There seems to be so many contradicting ideas on this topic.
I simply wish to have my menu in a UIViewController and my game in the SKScene
In my SKScene I used:
self.removeFromParent()
self.view?.presentScene(nil)
The nodes are removed but the scene is still in place as I still have the grey background and fps counter. Can I return to the View aspect of the UIViewController and hide the scene?
My method one implementation:
RootViewController:
class RootViewController: UIViewController {
var menu = MenuViewController()
var game = GameViewController()
override func viewDidLoad() {
super.viewDidLoad()
print("root")
MenuPresent()
}
func GamePresent() {
self.addChildViewController(game)
self.view.addSubview((game.view)!)
game.didMoveToParentViewController(self)
}
func MenuPresent() {
self.addChildViewController(menu)
self.view.addSubview((menu.view)!)
menu.didMoveToParentViewController(self)
}
func menuDismiss() {
menu.willMoveToParentViewController(nil)
menu.removeFromParentViewController()
menu.view.removeFromSuperview()
}
}
MenuViewController:
class MenuViewController: UIViewController {
//var root = RootViewController()
override func viewDidLoad() {
super.viewDidLoad()
print("menu")
}
}
The print("menu") appears in my console, But the actual view and all assets of the MenuViewController do no appear.
On the other hand my GameViewController and it's SKScene work fine.
Views and scenes are two different things. Scenes are held inside of a view. You would have to simply present a scene within your menu view or transition to another view and present an SKScene from there. The code to present the scene might look like this:
let scene = GameScene(fileNamed: "GameScene")
let skView = self.view as! SKView
scene?.scaleMode = .AspectFit
skView.presentScene(scene)
First you need to know that SKScene and UIViewController are totally two different things. The hierarchy is typically as following:
UIViewController --> UIView(SKView) --> SKScene
So you SKScene is presented in a SKView which can be a UIView, then the UIView is presented in a UIViewController.
Once you know the hierarchy, everything is easy. There are many ways to use different UIViewController for menu and GameScene stuff.
Method One
For example, you can have a RootViewController, a GameViewController and a MenuViewController. The RootViewController is the initial ViewController when the app launches.
In the RootViewController, you can create a function to present the GameViewController:
func setupGameViewController() {
self.gameViewController = GameViewController()
self.addChildViewController(gameViewController!)
self.view.addSubview((gameViewController!.view)!)
gameViewController?.didMoveToParentViewController(self)
}
You need to present you SKScene in GameViewController, I guess you should be familiar with this step.
Then when you need to display the menu, you can add the MenuViewController to RootViewController with a function like:
func setupMenuViewController() {
self.menuViewController = MenuViewController()
self.addChildViewController(menuViewController!)
self.view.addSubview((menuViewController!.view)!)
menuViewController?.didMoveToParentViewController(self)
}
You also need to present you menuView in this ViewController, which I suppose you already know.
Also create a function to dismiss the MenuViewController:
func removeMenuViewController(){
self.menuViewController?.willMoveToParentViewController(nil)
self.menuViewController?.removeFromParentViewController()
self.menuViewController?.view.removeFromSuperview()
}
And everything is done.
Method Two
You can also have only one UIViewController, but create you menu as a UIView, then you can use self.view.addSuview(menuView) to present your menu. Of course you root view, which is the self.view as SKView is still there, but it doesn't matter because it's hidden behind the menuView.
A Note for your updated question
You cannot remove the scene because the scene is actually self.view as SKView, self.view is the root view of a UIViewController, it cannot be removed. If you really want to remove you scene (for most cases, it's not necessary), you can create a new SKView that present your SKScene, then add this SKView to your UIViewController by self.view.addSubview(skView), when you want to completely remove the scene, just use skView.removeFromSuperview().

How can I dismiss a ViewController from my GameScene.swift?

QUESTION: How can I dismiss a ViewController from my GameScene.swift ?
SITUTATION: I have 2 VCs in my SpriteKit Game, like so:
ViewController.swift ----Press Play-----> GameViewController
When the player loses, I want to dismiss the GameViewController so the player can press play again. I check for the player's loss in my GameScene.swift and would like to dismiss the GameVC from there.
N.B.: Googled this without success.
WHAT I TRIED:
1) Creating a gameVC instance in my GameScene.swift and dismissing it like so:
let gameVC = GameViewController()
gameVC.dismissViewController(false,completion: nil)
2) Doing:
self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
Those don't work for obvious reasons ^^
You don't want to "grab" the existing instance: https://pragprog.com/articles/tell-dont-ask
You need to either hand GameScene a reference to the view controller so it can dismiss it, or use the delegate pattern to communicate backwards to a controlling object that the VC should be dismissed/dismiss itself.
A simple example… you can add a GameViewController property to GameScene, then dismiss the VC at the appropriate time:
class GameScene: SKScene {
var gameVC: GameViewController?
func gameDidEnd() {
gameVC?.dismissViewControllerAnimated(true) {
// if desired, do any cleanup after the VC is dismissed
}
}
}
Then, just set this property when creating the GameScene object in the first place:
if let gameScene = GameScene(fileNamed: "MyScene") {
gameScene.gameVC = someGameVC
}
This simple approach will tightly couple GameScene and GameViewController, making it a bit more difficult if you ever want to use one of these objects without the other. But for this simple use case, it may be fine.
I've follow some of your discussion. I want to add some code, because usually I prefeer to work with one ViewController or two and many SKScene and SKNode, but in this case could be useful to have a currentViewController reference:
class MyModelScene: SKScene {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var currentViewController : MyModelViewController! = MyModelViewController()
// MyModelViewController is a customized UIViewController
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
print("---")
print("∙ \(NSStringFromClass(self.dynamicType))")
print("---")
}
}
class Level1Scene: MyModelScene {
...
}
In the UIViewController:
class PreloadViewController: MyModelViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = Level1Scene(fileNamed:"Level1Scene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsPhysics = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill
scene.currentViewController = self
skView.presentScene(scene)
}
}
}
With this code , you've always a currentViewController reference in your SKScene and you can check if it's the correct viewController you want to dismiss or not.

Resources