I am adding a UIView to the view of an SKScene. Later, when I wish to remove that UIView form its superview, using the standard method of uiview.removeFromSuperview does not seem to work. How should I be accomplishing this instead? Here is how I add the UIView:
func addContainerView() {
let containerRect = CGRectMake(400, 24, 600, 720)
smallerView = UIView(frame: containerRect)
smallerView.backgroundColor = UIColor.redColor()
self.view.addSubview(smallerView)
}
Here is how I am attempting to remove it:
func removeContainerView() {
smallerView.removeFromSuperview()
}
This all takes place within the SKScene class, so here 'self' refers to that scene.
Any thoughts?
First of all I wonder which version of swift your are using.
self.view is optional in 1.2
hence your should type: self.view?.addSubview() if you are targeting swift 1.2
I have tried in swift 1.2 to make a simple app
class GameScene: SKScene {
let subview = UIView()
override func didMoveToView(view: SKView) {
subview.frame = CGRectMake(0, 0, 100, 100)
subview.backgroundColor = SKColor.orangeColor()
self.view?.addSubview(subview)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
removeContainerView()
}
func removeContainerView() {
subview.removeFromSuperview()
}
}
The above code works very well.
I can think of a couple of reasons your view doesn't get removed
Are you sure that removeContainerView is actually called. Try to make a break point to see if it's called
If you have set up your SKView in code something might have been setup wrong.
Your subview is being deallocated or something
To fully debug your problem we need to see some more code.
What we need is:
Declaration of your subview
All functions that call removeContainerView()
Even better would be to pastebin your SKScene class.
Related
I have came across a very unusual problem with - to my understand - swift 3. I am create a SpriteKit iOS game. The problem is with a SKSpriteNode. I create the node by
var gameOverBackground : SKSpriteNode!
This is located just inside the class. I then initialize the node in the didMove() function like so
override func didMove(to view: SKView) {
gameOverBackground = SKSpriteNode.init(texture: SKTexture(image: #imageLiteral(resourceName: "UIbg")), size: CGSize(width: (self.scene?.size.width)! / 1.5, height: (self.scene?.size.height)! / 1.5))
gameOverBackground.position = CGPoint(x: 0, y: 0)
gameOverBackground.zPosition = 10
gameOverBackground.name = "GameOverBackground"
}
Now the problem arises when I later try and add the node as a child to the scene. I am doing this in a contact function like so
func didBegin(_ contact: SKPhysicsContact) {
if (contact with many nodes) {
self.addChild(self.gameOverBackground)
}
}
I keep getting the error that the node is equal to nil. I have found a work around by just simply adding the child node in didMove() and making it hidden. Then just making it unhidden when contact happens; but I was more curious on why this problem is happening and if i'm doing something wrong.
Thanks in advance!
I have found a work around by just simply adding the child node in didMove() and making it hidden. Then just making it unhidden when contact happens; but I was more curious on why this problem is happening and if i'm doing something wrong.
This means that your node is notnil in didMove, but then somewhere along the way has become nil by the time of your contact delegate.
Here is an example of your code, only simplified a bit:
var node: SKSpriteNode!
override func didMove(to view: SKView) {
node = SKSpriteNode(color: .blue, size: CGSize(width: 25, height: 25))
}
// Touchesbegan on ios:
override func mouseDown(with event: NSEvent) {
addChild(node)
}
The code runs fine and when you click the screen the node appears.
Touchesbegan / didBegin won't make a difference.
The issue is that somewhere in your code the node is getting set to nil.
Here is an example of how that could happen:
// Touchesended on ios:
override func mouseUp(with event: NSEvent) {
node.removeFromParent() // or, node = nil
addChild(node) // crash!!
}
I wanna implement a game platform and have implemented three swift games(by using single view project) individually. Now I would like to implement a main menu that has three buttons: Game A, Game B and Game C. When users press the button, the correspondent game will start. Could you please tell me how to do that with less efforts? Like Java, we could create a jar for each project and call them later in other java project. Is there similar way in swift? Thanks.
If you have all the code create an unique project. You should create a segue in every buttonTapped method. To do this create a segue in the Storyboard (drag from one VC to another), then click on it and give it an identifier. In the buttonTapped method use performSegueWithIdentifier("your_identifier") and that is it.
Edit:
Ok, i'll try to be more specific to help you. To create the app you want you will need all the code in the same project (all about the three games).
So, in Xcode, you will have to create four viewController (three for the games and another one for the menu). To do this go to your Main.storyboard file (you have it when you create the project by default) and drag the viewController view (right down). Then, you will have to create a custom class for every one of them (inherits from UIViewController). Then in the storyBoard again you choose your custom class (the one you've created) for every viewController.
Talking about the segues, you can learn how to create them here.
It's basically a way to move between your viewControllers. When you create one you have to use performSegueWithIdentifier("yourSegueIdentifier", sender: nil)
Put that code inside the buttonTapped method of your button (learn all about it here).
Well, let me know if you don't understand something. Good luck!
You can create a SpriteKit scene as a main menu for your game, with three different buttons for transition to each game scene. You can create a title using the SKLabelNode and use a Sprite or Shape Node as buttons for your scene.
I experienced adding UIButtons and UILabels to a SpriteKit Scene can get very technical with the problem of positioning them. Due to the fact that the UI Objects are positioned on the view and not on the SpriteKit Scene directly. You can use a SKSpriteNode as a Button and the SKLabelNode as a Title, For a menu scene.
A Sprite kit scene is placed on the UIView and is scaled depending on the scale mode you define. Apples default scale mode .aspectFill requires no adjustment to Sprite kit objects positioning on different Phone device screen sizes.
This is a Custom Class of a SKSpriteNode with the same functionality as a button.
import Foundation
import SpriteKit
class ButtonLabelNode : SKSpriteNode {
let buttonPressed: () -> ()
init(texture: SKTexture?, color: UIColor, size: CGSize, text: String, buttonPressed: #escaping () -> ()) {
self.buttonPressed = buttonPressed
super.init(texture: texture, color: color, size: size)
let label = SKLabelNode(fontNamed: "Futura")
label.fontSize = 50
label.fontColor = SKColor.red
label.position = CGPoint.init(x: 0.0, y: 0.0)
label.zPosition = 1
label.verticalAlignmentMode = .center
label.text = text
self.addChild(label)
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.alpha = 0.8
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.alpha = 1.0
buttonPressed()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When a touch begins on the SpriteNode the alpha decreases to 0.8 and back to 1.0 as the touch ends, Giving it the same visual affect of a UIButton. In the overridden function 'touchesEnded' there is a function that will be called every time the button is pressed, that function is added in the initializer and can be initialized in your game scene.
override func didMove(to view: SKView) {
let labelNode = LabelNode(texture: nil, color: .white, size: CGSize.init(width: 200, height: 100), text: "Play", buttonPressed: playButton)
labelNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(labelNode)
}
func playButton()
{
print("play")
}
If you would like to have buttons of different shapes or a unique shape you can sub class a ShapeNode instead of a SpriteNode and Implement the same Functionality, Here is a example of how to Create a Circle ShapeNode as a button.
import Foundation
import SpriteKit
class SKShapeButton: SKShapeNode {
init(circleOfRadius: CGFloat) {
super.init()
let diameter = circleOfRadius * 2.0
self.path = CGPath(ellipseIn: CGRect(origin: CGPoint.init(x: 0.0 - circleOfRadius, y: 0.0 - circleOfRadius), size: CGSize(width: diameter, height: diameter)), transform: nil)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.alpha = 0.8
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.alpha = 1.0
}
}
Then just make a instance of it in your game scene.
override func didMove(to view: SKView) {
let shapeButton = SKShapeButton(circleOfRadius: 50)
shapeButton.fillColor = .red
shapeButton.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(shapeButton)
}
To transition to each of your game scene's you can implement the code that is in the Game View Controller swift file, That presents the provided game scene.
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
}
I'm trying to create a subclass of SKLabelNode for a button in SpriteKit. I tried to create a button using SKLabelNode as a parent, so I can use everything I know about labels while creating my buttons (font, text size, text color, position, etc).
I've looked into Swift Spritekit Adding Button Programaticly and I'm using the basis of what that is saying, but rather I'm making a subclass instead of a variable, and I'm creating the button using a label's code. The subclass has the added function that will allow it to be tapped and trigger an action.
class StartScene: SKScene {
class myButton: SKLabelNode {
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if myButton.containsPoint(location) {
// Action code here
}
}
}
}
override func didMoveToView(view: SKView) {
let startGameBtn = myButton(fontNamed: "Copperplate-Light")
startGameBtn.text = "Start Game"
startGameBtn.fontColor = SKColor.blackColor()
startGameBtn.fontSize = 42
startGameBtn.position = CGPointMake(frame.size.width/2, frame.size.height * 0.5)
addChild(startGameBtn)
}
}
My error is in: if myButton.containsPoint(location)
The error reads: Cannot invoke 'containsPoint' with an argument list of type '(CGPoint)'
I know it has to do something with my subclass, but I have no idea what it is specifically.
I also tried putting parentheses around myButton.containsPoint(location) like so:
if (myButton.containsPoint(location))
But then the error reads: 'CGPoint' is not convertible to 'SKNode'
Thanks
I have an alternative solution that would work the same way.
Change the following code
if myButton.containsPoint(location) {
To this and it should compile and have the same functionality
if self.position == location {
I have a view with a delegate that I want to have call numpadView(numpadView:) in GameController upon button press, however I can't get it to work. The overload of touchesBegan() works fine, it's really the line with pressDelegate?.numpadView(self) which doesn't call the delegate function in the GameController class. I'm stumped as to what's missing to get it to work?
I cleaned the code to leave only the essential related to the problem for simplicity.
NumpadView.swift
protocol NumpadPressDelegateProtocol {
func numpadView(numpadView: NumpadView)
}
class NumpadView: UIButton{
var pressDelegate: NumpadPressDelegateProtocol?
init(char: Character) {
let frame = CGRectMake(160, 100, 50, 50)
super.init(frame:frame)
self.setTitle("\(char)", forState: UIControlState.Normal)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
pressDelegate?.numpadView(self)
}
}
GameController.swift
class GameController: NumpadPressDelegateProtocol {
func numpadView(numpadView: NumpadView) {
//do something
}
}
Declaring GameController as NumpadPressDelegateProtocol is not enough for you to get a callback. You also need to set the pressDelegate of the NumpadView instance inside the Gamecontroller. Assuming numpadView is the instance variable be it IBOutlet or not, you have to set the delegate like
Inside GameController init
numpadView.pressDelegate = self
Have you set the pressDelegate property to something? Nothing is assigned to it in the code you've shown. Do you assign something to it elsewhere in your code?
I'm attempting to convert the Ray Wenderlich's tutorial on UIDynamics to swift code, but I'm stuck on the very first part.
http://www.raywenderlich.com/50197/uikit-dynamics-tutorial
Here's what I'm doing:
created a square UIView
added a UIDynamicAnimator to the main view
added a UIGravityBehavior and initialized it with the square
added gravity behavior to the animator
It all compiles and runs fine but the square doesn't move. Can anybody see what I'm doing wrong?
https://github.com/oisinlavery/UIDynamics-Swift
You've created your animator as a local variable to your go method, so it disappears as soon as that method is finished. It needs to be an instance variable on your ViewController class so that it will stick around and do the work of animating the square:
class ViewController: UIViewController {
var animator: UIDynamicAnimator?
#IBAction func go(sender : UIButton) {
var square = UIView(frame: CGRectMake(100, 100, 100, 100))
square.backgroundColor = UIColor.blackColor()
self.view.addSubview(square)
self.animator = UIDynamicAnimator(referenceView: self.view)
var grav = UIGravityBehavior(items:[square])
self.animator!.addBehavior(grav)
}
}