Swift: How to handle view controllers for my game - ios

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.

Related

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.

how to present a uiviewcontroller over a spritekit scene

Hi I have seen many questions about this topic but none seem to solve my situation. I want to present a UIView controller over an SKScene when the user win/dies. I created a PresentWinLoseViewController : UIViewController subclass and connected it in story board. I created a segue from GameViewController to PresentWinLoseViewController in storyboard plus two exit segues from PresentWinLoseViewController back to GameViewController.
Here is how I am calling these. In my SKScene when I win/lose
func win() {
let gameVC = GameViewController()
gameVC.performSegueWithIdentifier("tryAgain", sender: self)
}
and my exit segues from PresentWinLoseViewController
#IBAction func tryAgainButtonPressed(segue:UIStoryboardSegue) {
self.performSegueWithIdentifier("unwindTryAgain", sender: self)
}
but I cant even get that far bc the first segue never works. I keep getting
Receiver (<SpriteLevelBuilder.GameViewController: 0x7ff2b5e49210>) has no segue with identifier 'tryAgain''
what am i doing wrong?
For starters, GameViewController should already be initialized, there is no need to let gameVC = GameViewController()
This is because your SKScene is actually presented by your GameViewController. Go look in your GameViewController and you should see the initialization process of SKScene in the viewDidLoad().
Since the GameViewController is already initialized, all you need to do is directly call performSegueWithIdentifier on the GameViewController from your SKScene.
I just did a quick search and this answer seems to apply to your situation: How to call method from ViewController in GameScene
I would do this by placing the elements that you would like to come on over top your scene in the same view controller just on a level above the one with your scene. Using a UIImage View is a great way to make the background for the new elements. Then just create a function for showing the elements and one for hiding them. For all UI elements .hidden = true would be the way to hide the elements and .hidden = false would be the way to show the elements. Here is an example of some code in one of my apps.
//this is the function that hides the settings menu
func hideSettingsMenu() {
settingsCoverImage.hidden = true
settingsTitle.hidden = true
watchButton.hidden = true
fitBitButton.hidden = true
backButton.hidden = true
classesButton.hidden = true
settingsTopBackground.hidden = true
}
//this is the function that shows the settings menu
func showSettingsMenu() {
settingsCoverImage.hidden = false
settingsTitle.hidden = false
watchButton.hidden = false
fitBitButton.hidden = false
backButton.hidden = false
classesButton.hidden = false
settingsTopBackground.hidden = false
}
By calling those functions It simply shows or hides the elements for that page. I am currently working on animating the transitions between pages. Doing it this was also gives you the option to add transparency, giving your app good looks ;)

UIViewController load and release strategy

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?

Showing keyboard on SKScene and performing action on a sprite node based on key tapped

I am trying to learn SpriteKit and play with some simple sample apps.
Currently I am trying to achieve below things:
Show keyboard over SKScene
If user taps 'A', perform some action on a sprite node, if user taps 'B' perform some other action on other sprite nodes
To achieve 1st requirement I tried below steps:
Step 1: In GameViewController.swift, added a reference to the view.
var keyView: KeyboardDummyView?
Step 2: In viewDidLoad(), created the view, assigned it to the ivar, and added it to the controller's view. Also assigned "self" to a backreference ivar in GameScene.
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// So scene can forward touch event back here.
scene.gvc = self
// Other config
keyView = KeyboardDummyView(frame: self.view.frame)
self.view.addSubview(keyView!)
}
}
Step 3: Added a function to handle a tap/touch.
func handleTap() {
// Show the keyboard
keyView!.becomeFirstResponder()
}
Step 4: In GameScene.swift added the reference to the controller.
var gvc: GameViewController?
Step 5: In touchesBegan handled tap.
gvc!.handleTap()
But for some reasons it is not showing the keyboard when screen is tapped :(
Here is the code base: KeyboardOverSKScene
Please suggest if I am doing any thing wrong?
Note: I am trying this in Xcode 6.0.1

Resources