UIViewController load and release strategy - ios

I’m building a game with SpriteKit and Swift based on the logic of navigation described below.
(source: noelshack.com)
The code below illustrate the logic put in place to go from one screen to another (here to go from the Startup Screen to either the Preferences Setting Screen or the Level Choice Screen)
class StartupScreen: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
let StartView = self.view as SKView
let StartScene = StartSceneClass(size: self.view.bounds.size)
StartView.ignoresSiblingOrder = true
StartScene.scaleMode = .AspectFill
StartView.presentScene(StartScene)
}
+ some other code
}
class StartSceneClass: SKScene
{
+ some init and game logic code…
override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
{
for touch: AnyObject in touches
{
let location = touch.locationInNode(self)
switch self.nodeAtPoint(location)
{
case self.playButton:
let vc = self.view?.window?.rootViewController?.storyboard!.instantiateViewControllerWithIdentifier("ChoixLevelVC") as ChoixLevelVC
self.view?.window?.rootViewController?.presentViewController(vc, animated: true, completion: nil)
case self.BoutonPrefs:
let vc = self.view?.window?.rootViewController?.storyboard!.instantiateViewControllerWithIdentifier("PlayLevelVC") as PlayLevelVC
self.view?.window?.rootViewController?.presentViewController(vc, animated: true, completion: nil)
default:
break
}
}
}
}
I use the same logic as above to go from the Level Choice Screen to the Play Level Screen.
The problems I have are :
When I go from the Level Choice Screen to the Play Level Screen, I
see the Startup Screen below during the transition
When I quit the Play Level Screen, I go back directly to the Startup
Screen and not to the Level Choice Screen where I came from
The second time I go from the Level Choice Screen to the Play Level
Screen, I find back all the sprites of the previous time. The SKScene
of the Play Level Screen is not deleted when I dismiss the
UIViewController.
Regarding the last problem, what I want is to completely kill/erase the Play Level Screen each time I quit it.
So far I have implemented self.view?.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil) in either the SKScene or the UIViewController of the called UIViewController to dismiss the view controller.
I have read a lot of stuff regarding the way to dismiss a UIViewController and I have understood that the recommended way is to do it from the calling ViewController (In my case it means that I should dismiss the Play Level Screen from the Level Choice Screen). However, I couldn’t find a Swift example of this way of doing it (lot of Obj-C examples that I can’t understand).
I want to use the less possible the storyboard and to do most of the tasks programmatically. Today I just use the storyboard to create each ViewController and load it because I couldn’t find a way to code everything yet.
Thanks for any link or advice on the best way to properly implement this kind of navigation logic.

When I go from the Level Choice Screen to the Play Level Screen, I see the Startup Screen below during the transition
This sounds to me like the UIViewController you are pushing to has a clear background, in that case, you are still being able to see the underlaying VC.
When I quit the Play Level Screen, I go back directly to the Startup Screen and not to the Level Choice Screen where I came from
Are you using popViewController or popToRootViewController? When using popViewController it should work as expected.
The second time I go from the Level Choice Screen to the Play Level Screen, I find back all the sprites of the previous time. The SKScene of the Play Level Screen is not deleted when I dismiss the UIViewController.
Correct me if i'm wrong, but in my understanding when you pop a UIViewController, it stays on the heap / in memory for a while until another UIViewController is pushed on to the UINavigationController.
You could try to empty your sprites in viewWillDisappear?

Related

Swift: How to handle view controllers for my game

i have a general question about view controllers and how to handle them in a clean way when i develop a SpriteKit based game.
What i did so far:
Use storyboard only for defining view controllers
SKScene's are presented in each view controller (Home, LevelSelection, Game) by presentScene
in each view controller i call performSegueWithIdentifier with the identifier i defined in the storyboard between the view controllers
all the content i show programmatically using SKSpritenode etc. on the SKScene's
on the storyboard i only have view controllers with segue relations and identifiers defined
all the stuff i do in viewDidDisappear is because it seems to be the only way to get my SKScene deinited correctly
My problems are:
everytime i segue to another view, my memory raises, because the view controller is re-initialized, the old one keeps staying in the stack
it is not clear for me how to handle the segue's between the view controllers, on some tutorial pages i see people using the navigation controller, others are using strong references of some view controllers and using the singleton pattern for the view controller in order to decide either to init the view controller or just show it
my view controllers are not deiniting, i understand my home view can't because it is the initial one, but since ios is reiniting it anyways, why then not unloading it?
What is the correct way for a Swift based game using SpriteKit to handle the view controller? Below you can see my initial view controller (Home) showing an SKScene with a simple play button which calls the play() function to segue to the levelselection
import UIKit
import SpriteKit
class Home : UIViewController {
private var scene : HomeScene!
override func viewDidLoad() {
print(self)
super.viewDidLoad()
self.scene = HomeScene(size: view.bounds.size)
self.scene.scaleMode = .ResizeFill
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(play), name: Constants.Events.Home.play, object: nil)
skView.presentScene(self.scene)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
let v = view as! SKView
self.scene.dispose()
v.presentScene(nil)
NSNotificationCenter.defaultCenter().removeObserver(self)
self.scene = nil
self.view = nil
print("home did disappear")
}
func play() {
self.performSegueWithIdentifier("home_to_levelselection", sender: nil)
}
deinit {
print("Home_VC deinit")
}
}
Your way seems very complicated to essentially present 3 scenes. Its not what you are supposed to do for SpriteKit games, you only really need 1 view controller (GameViewController).
Load your first scene from GameViewController (e.g HomeScene) and nothing else.
Create your playButton and other UI directly in HomeScene. Use SpriteKit APIs for your UI (SKLabelNodes, SKNodes, SKSpriteNodes etc).
You should never really use UIKit (UIButtons, UILabels) in SpriteKit. There are some exceptions to this, like maybe using UICollectionViews for massive level select menus, but basic UI should be done with SpriteKit APIs.
There is plenty tutorials to google on how to create sprite kit buttons, how to use SKLabelNodes etc. Xcode has a SpriteKit level editor so you can do all that visually similar to storyboards.
Than from HomeScene transition to the LevelSelect Scene and than to the GameScene and vice versa. Its super easy to do.
/// Home Scene
class HomeScene: SKScene {
...
func loadLevelSelectScene() {
// Way 1
// code only, no XCode/SpriteKit visual level editor used
let scene = LevelSelectScene(size: self.size) // same size as current scene
// Way 2
// with xCode/SpriteKit visual level editor
// fileNamed is the LevelSelectScene.sks you need to create that goes with your LevelSelectScene class.
guard let scene = LevelSelectScene(fileNamed: "LevelSelectScene") else { return }
let transition = SKTransition.SomeTransitionYouLike
view?.presentScene(scene, withTransition: transition)
}
}
/// Level Select Scene
class LevelSelectScene: SKScene {
....
func loadGameScene() {
// Way 1
// code only, no XCode/SpriteKit visual level editor used
let scene = GameScene(size: self.size) // same size as current scene
// Way 2
// with xCode/SpriteKit visual level editor
// fileNamed is the GameScene.sks you need to create that goes with your GameScene class.
guard let scene = GameScene(fileNamed: "GameScene") else { return }
let transition = SKTransition.SomeTransitionYouLike
view?.presentScene(scene, withTransition: transition)
}
}
/// Game Scene
class GameScene: SKScene {
....
}
I strongly recommend you scratch your storyboard and ViewController approach, and just use different SKScenes and 1 GameViewController.
Hope this helps
Go to the segues and use Show Detail Segues anywhere that you don't want the previous view controller to be kept in the stack. Keep in mind that you will have to reinitialize everything appropriately in those view controllers whenever you do return to them.
If you pay attention, viewDidAppear loads every time that you see the view appear, while with your current setup, viewDidLoad would only be called initially and if you returned to the viewController, only viewDidAppear would be called.
When you use a segue to transition out of a viewController, prepareForSegue is called, but deinit() is only called when you use a show detail segue (or custom segue with those specific properties about it), because the view, like you said, is loaded into memory so it can be retrieved easier.

Adding a screen before GameScene.swift in SpriteKit

I'm making a game in XCode using Swift and I want to add a starting screen before the actual game. I don't want just an image that fades out, I want a screen with "play", "select character", and some buttons. I don't know if i should add a new swift file, add a sks file, edit the GameScene.swift file? I'm just starting to develop in SpriteKit and it would be great if you could explain me how to do this properly. Thanks.
Follow this Step by step and it should work:
Open up your Interface Builder (main.storyboard) and add a UIViewController.
Select the new UIViewController, go to Editor > Embed In > UINavigationController
a. To remove the UINavBar for your game scene you'll be able to do this using the property navigationBarHidden in the Game Scene View Controller
Add the UIButtons and/or other controls of your choice to your new View Controller
Left Click (ctrl) and drag the PLAY UIControl to the View Controller displaying your Game Scene.
Connect the other UIControls after adding other View Controllers that are associated with their UIControl counterpart. (see below)
If you've only ever programmed using the SpriteKit Template file, I would recommend looking over this Apple Documentation
~ MORE Details ~
Having successfully added your Start Screen, or your Root View Controller, add one ViewController for each page, or screen, that you'll need the Start Screen to display, by dragging one at a time into the Storyboard file.
Having all the Screens you'll need represented by the View Controllers within your main.storyboard file, the next step is Control Clicking from each UIButton on your "Start Screen" to the ViewController that will display its target screen.
You can just create new sprite kit scene and new swift file,
then put class of your swift file into Custom Class inspector in the
gamescene.sks so it will work just like default GameScene.
then initialise your menu scene in viewDidLoad instead of original GameScene
if let menu = SKScene(fileNamed: "MainMenu") {
menu.scaleMode = .aspectFill
view.presentScene(menu)
}
You can switch to your original scene like this(for example):
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let posistion = touch.location(in: self)
let node = self.atPoint(posistion)
if node == yourNode {
if let originalScene = SKScene(fileNamed: "GameScene") {
originalScene.scaleMode = .aspectFill
view?.presentScene(originalScene)
}
}
}
}

iOS - Programatically load view on app didFinishingLaunching

This question is mostly about how I should structure my app. When the app loads the user can select from a list of videos to play, however the actual video player is on a different view controller. At the moment I am just keeping that view controller in memory so that the video can play continuously while the user is navigating throughout the app.
So the problems is that if the user selects a video before loading that movie view controller, nothing will happen of course.
How should I structure my app so that the video can play continuously whether or not the movie player view controller is held in memory? Is this possible?
I'm not 100% sure what you're trying to do; but a default project created in XCode will "Programatically load view on app didFinishingLaunching" without any further coding needed.
Generally you should structure your app with an initial view or view controller (like a navigation controller); and start any visible actions in the "viewDidLoad" method of the target view.
In your case, I would recommend loading straight to the video controller first; and then programmatically segueing to the video selector view if no video is selected.
Usually, I create a structure like this in my projects, I think you need to take a special look in OverlayViewController, check out a example:
MainViewController - As rootViewController
|- content (Any view controller to be presented, yes inside MainViewController)
|- OverlayViewController (a view controller over content )
This structure allows you to change contents without change entire hierarchy.
At MainViewController you will need a method to change current content to another, there is a simple example of how can you do that:
// PS I'm not sure if this will work in the first try, I wrote from my mind right now :)
func changeViewController (controller: UIViewController) {
let from: UIViewController
if childViewControllers.count > 0 {
from = childViewControllers.first as! UIViewController
}
else {
presentViewController(controller)
return
}
let transitionContext = SomeViewControllerContextTransitioning (
fromViewController: from,
toViewController: controller
)
transitionContext.onAnimationComplete = { success in
if !success {
// TODO: Error fallback
}
else {
from.view.removeFromSuperview()
from.removeFromParentViewController()
controller.didMoveToParentViewController(self)
}
}
from.willMoveToParentViewController(nil)
addChildViewController(controller)
SomeAnimator().animateTransition(transitionContext)
}
And finally, in your MainViewController you can observe or control your video playback.
I hope this can help you.

In Swift on "game over" move from scene to another UIView and dispose the scene?

I have a "life counter" in my game and when it hits 0 = dead - i want to move away from the scene to another UIView - which is game over view - with stats and a button to go back to the home screen (first UIViewController with buttons to start the game and so on.
Here is the code of how i move to the Game Over view
class GameScene: SKScene,SKPhysicsContactDelegate {
var viewController: UIViewController?
// more code and functions
// ......
func trackLife (lifeCHange: Int){
life = life + lifeCHange
lifeLabel.text = String(life)
if life < 1 {
// Go to Game Over VC
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
self.viewController!.performSegueWithIdentifier("gameOverSegue", sender: viewController)
}
}
}
this works for presenting the game over view, but what i thinks i'm not "disposing" or reseting the scene. because if i do this in a loop:
Start Game --> Game Over --> Back to The Home Screen --> Start Game --> Game Over....
I see the memory usage grows on each cycle:) I guess i'm just adding scenes but not removing them?
I'm sorry - i'm fresh to this. Will be very grateful for your experience!:)
To manage your memory effectively in Sprite-Kit, you should create another SKScene for your GameOver screen to be presented from your main screen. In this way, the old SKScene would be released.

Launch Watch App into middle view

Basically, my app is laid out in the page format and I would like it to launch into the middle of the three pages. There is no way of setting a previous page segue, so I have been trying to do it in code.
I have the main view set to the first view, and I have tried a variety of methods to segue to the middle view as soon as the app is launched.
Here is the two ways I tried:
if segueCheck == true {
self.pushControllerWithName("budget", context: self)
self.presentControllerWithName("budget", context: self)
segueCheck = false
}
The first presents the view, but as a completely separate view, and the second replaces the first view with the middle view.
Does anyone know how I can launch into the middle view and allow the user to swipe left and right of it?
Thanks.
WKInterfaceController's becomeCurrentPage() should be what you're looking for.
Let's create a new class for the center view controller, CenterPageViewController, and change its initWithContext: method as follows
import WatchKit
class CenterPageViewController: WKInterfaceController {
override init(context: AnyObject?) {
super.init(context: context)
super.becomeCurrentPage()
}
}
Now let's set the Custom Class for the middle page in your storyboard to CenterPageViewController
and finally hit run.
You won't be able to get rid of the initial transition from the left page to the center page, but the app will finally begin on the middle page.
Update Swift 3.0
class CenterPageViewController: WKInterfaceController {
override init (){
super.init()
super.becomeCurrentPage()
}
}
This will works...!!!
Thanks
The new way to do this in watchOS 4 and higher is:
WKInterfaceController.reloadRootPageControllers(withNames:
["Controller1" "Controller2", "Controller3"],
contexts: [context1, context2, context3],
orientation: WKPageOrientation.horizontal,
pageIndex: 1)
Now you don't get the annoying animation when using becomeCurrentPage() when you want to start with the middle page.

Resources