I'm making a small game in SpriteKit where I have a bool value stored that checks if it's the first time the user plays my game. If it is, I want the app to redirect him to another viewcontroller where he enters his character-name.
However, I'm having trouble getting my app to do so.
In my "GameViewController.swift" I have this code:
override func viewDidLoad() {
super.viewDidLoad()
checkGame()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func checkGame() {
//Check game
let firstPlay:Bool = appData.bool(forKey: "\(GameData.firstPlay)")
print(firstPlay)
if firstPlay == false {
print("Heading for setup")
self.performSegue(withIdentifier: "GoToSetup", sender: nil)
}
}
However, the "setup viewcontroller" isn't loaded. I can manage to tricker the segue with a button-click, but that's not what I want. I want it to happen as soon as the view is loaded.
Any ideas?
I don't know what you meant by "setup viewcontroller" isn't loaded", but usually we don't perform segues in the viewDidLoad. At this time, the view has just been loaded into memory and has not appeared yet. Performing a segue at this time is kind of a weird thing to do.
I recommend you to perform the segue in viewDidAppear():
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated: animated)
performSegue(withIdentifier: "GoToSetup", sender: nil)
}
You can programmatically call a segue with the following:
self.performSegueWithIdentifier("segueId", sender: self);
As it was said, in viewDidLoad stage UIViewController can't be done any UI-interaction, and performSegue to, because view is not at the view hierarchy yet. You can do this at viewDidAppear(animated:Bool).
Related
In my Xcode project, after signing out and performing a segue, my text fields and buttons are unresponsive and won't show the keyboard, editing mark or allow me to edit. I am not getting an error anywhere, however.
ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
imagePicker = UIImagePickerController()
imagePicker.allowsEditing = true
imagePicker.delegate = self
scrollView.isScrollEnabled = false
self.reset()
}
FeedVC.swift:
#objc func signOut(_ sender: AnyObject) {
KeychainWrapper.standard.removeObject(forKey: "uid")
do {
try Auth.auth().signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
performSegue(withIdentifier: "signOut", sender: nil)
}
Main.storyboard:
signOut Segue:
By the names you have picked, I’m guessing after signout what you really want to do is an unwind segue, basically returning to the original screen. The way you have it set up I believe, you’re actually creating a brand new view controller when you perform the segue from the tableview to the original view when really you want to go back to the one that has already been made.
What you actually want is an unwind segue, which will actually get you back to the original. This solves the problem mentioned by the other commenter (vicious cycle of segues). https://medium.com/#mimicatcodes/create-unwind-segues-in-swift-3-8793f7d23c6f
I built a Game with a pause button which shows a new modal ViewController with a Pause menu over the current and pauses the scene with the following function (which is located in the scene and called via protocol and delegate):
func pause(isPaused: Bool) {
let pauseAction = SKAction.run {
self.view?.isPaused = isPaused
}
self.run(pauseAction)
}
Pausing the scene and showing the modal view controller works, but when I return to the scene by a unwind segue the scene doesn't unpause.
The pause button is in the same view as the scene.
For communication with the scene from the Pause menu, I use an unwind segue where I call the pause function via Protocol and delegate.
I present the pause menu with that:
#IBAction func PauseMenuButton(_ sender: Any) {
let vc = storyboard!.instantiateViewController(withIdentifier: "PauseMenuVC")
vc.providesPresentationContextTransitionStyle = true
vc.definesPresentationContext = true
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
SceneIsPausedDelegate.pause(isPaused: true)
}
and go back to the scene with that :
#IBAction func unwindToGameVC(segue: UIStoryboardSegue) {
SceneIsPausedDelegate.pause(isPaused: false)
}
I think that the function is never called because it is in the scene since it is paused the is no execution.
SKView pause and SKScene pause behave differently
When an SKView is paused, it will not call the update functions on the scene
When an SKScene is paused, SKView will still call the update functions on the scene, and the scene will not call the update functions on the children nodes.
In your scenario, you are calling pause on the SKView. This means any action you run on your scene will not fire because update never happens.
I recommend either switching to SKScene pause, or not using an action to pause and unpause.
I changed:
func pause(isPaused: Bool) {
let pauseAction = SKAction.run {
self.view?.isPaused = isPaused
}
self.run(pauseAction)
}
to:
func pause(isPaused: Bool) {
self.view?.isPaused = isPaused
}
it wokes without an action.
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.
When you press a button a custom segue is performed to start a game:
self.performSegueWithIdentifier("segueToGame", sender: nil)
I'm using sprite kit. I have settings in the GameViewController and GameScene such that you play until you lose and when you do the gameScene sends a message to the view controller telling it to segue back to the initial view controller. I do this as follows:
let notificationCenter = NSNotificationCenter.defaultCenter()
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
/*
All code to set the scene is there
*/
skView.presentScene(scene)
notificationCenter.addObserver(self, selector: "segueToHomeScreen", name: "segueToHomeScreen", object: nil)}
override func viewDidDisappear(animated: Bool) {
if self.isBeingDismissed() {
println("Dismissed Vc")
}else {
println("still here")
}
}
func segueToHomeScreen(){
self.performSegueWithIdentifier("segueToHomeScreen", sender: nil)
}
in my GameScene the code I used when you lose the game is:
func segueToHomeScreen() {
notificationCenter.postNotificationName("segueToHomeScreen", object: nil)
}
The problem is the GameScene has not completely stopped when I segue back to the home screen. I found this out by two things. One, the viewDidDisapper function you see above, I get a log message of "still here". Two, in gameScene I used this
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
println("Scene On")
}
When you lose the game and you are back at the home screen this function is still running. How can I stop the ViewController and GameScene from running after performing the segue to the home screen?
Mot sure if this is related but I also get an error message:
Warning: Attempt to present * on * whose view is not in the window hierarchy!
Hi I have a spriteKit game set up where everytime the user dies Ill get a pop up ViewController with Try again button and some iAds set up.
I have a segue to the viewController and an unwind segue from the VC to the gameViewController.
When I call the unwind func I reinitialize the scene for different reasons. My question is, am I creating view over view which will eventually lead to a crash or am I correctly reinitializing the scene. I took the code from viewDidLoad and put it into a function called "setUp()" and I call that function from the unwindSegue.
check it out: (all in GameViewController)
var currentLevel: Int!
var gameScene = GameScene()
override func viewDidLoad() {
super.viewDidLoad()
currentLevel = gameScene.currentLevel
setUp()
}
#IBAction func perpareForUnwind(unwindSegue: UIStoryboardSegue) {
setUp()
}
func setUp() {
if let scene = GameScene.level(currentLevel) {
// 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
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
scene.viewController = self
}
}
You are not creating a new view controller if you are using the unwind segue correctly. From what it looks like that is the case. If you are still not confident you can log out println(\(self)) and make sure of it (My swift is rusty but I think that is how you log it out).
Your question title asks about view controllers but your question info asks about creating additional views. This isn't an issue either. When you do
let skView = self.view as SKView
You are getting the current view of the view controller and just casting it as an SKView. You are not recreating another view at all.
Hopefully that helps.