How do I programmatically add a button that will run an action when it's clicked? What code would be used?
I am used to just adding a button in the storyboard and running an IBAction from there.
Adding a button in SpriteKit and responding to taps on it is not quite as easy as it is in UIKit. You basically need to create an SKNode of some sort which will draw your button and then check to see if touches registered in your scene are within that node's bounds.
A really simple scene with just a single red rectangle in the center acting as a button would look something like this:
import UIKit
import SpriteKit
class ButtonTestScene: SKScene {
var button: SKNode! = nil
override func didMove(to view: SKView) {
// Create a simple red rectangle that's 100x44
button = SKSpriteNode(color: .red, size: CGSize(width: 100, height: 44))
// Put it in the center of the scene
button.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
self.addChild(button)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Loop over all the touches in this event
for touch in touches {
// Get the location of the touch in this scene
let location = touch.location(in: self)
// Check if the location of the touch is within the button's bounds
if button.contains(location) {
print("tapped!")
}
}
}
}
If you need a button that looks and animates like the ones in UIKit, you'll need to implement that yourself; there's nothing built in to SpriteKit.
I have created a class SgButton (https://github.com/nguyenpham/sgbutton) in Swift to create buttons for SpriteKit. You can create buttons with images, textures (from SpriteSheet), text only, text and background images/texture. For example, to create button with image:
SgButton(normalImageNamed: "back.png")
Create button with textures:
SgButton(normalTexture: buttonSheet.buy(), highlightedTexture: buttonSheet.buy_d(), buttonFunc: tappedButton)
Create round corner text button:
SgButton(normalString: "Tap me", normalStringColor: UIColor.blueColor(), size: CGSizeMake(200, 40), cornerRadius: 10.0, buttonFunc: tappedButton)
Mike S - Answer updated for - Swift 5.2
override func didMove(to view: SKView) {
createButton()
}
func createButton()
{
// Create a simple red rectangle that's 100x44
button = SKSpriteNode(color: SKColor.red, size: CGSize(width: 100, height: 44))
// Put it in the center of the scene
button.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
self.addChild(button)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
// Check if the location of the touch is within the button's bounds
if button.contains(touchLocation) {
print("tapped!")
}
}
You can use OOButtonNode.
Text/Image buttons, Swift 4.
func tappedButton(theButton: UIButton!) {
println("button tapped")
}
}
The above code prints out button tapped when the button is tapped.
P.S. the swift ebook is a really good guide for the new programming language.
Related
So I have made 2 games so far and I always used the touchesBegan function and SpriteNodes to create a Menu, like that:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if (atPoint(location).name == "playButton"){
startGame()
}
}
}
But this way is pretty ugly because as soon as you touch the button it will call the according action to it and you can't cancel it. But with a normal UIButton it will only call the action after you take the finger off the button, and you can even cancle a button click by just moving your finger away from the button. And the problem is, I don't know how I can add a UIButton to my MenuScene.swift file and I also don't use the Storyboard because it's just confusing to me and I don't understand how the .sks, .swift and .Storyboard files are linked together, it just makes no sense to me. I mean the .sks and .storyboard files are both for buildung a GUI but in .sks you cant add a UIButton... but why??? Is there any convenient way to add a button?
I wouldn't use UIButton in my Spritekit code, you are better off to create your own Button class in Spritekit and use it. The sks file is not linked to a storyboard it is linked to any class that you designate (GameScene, MenuScene) it can even be linked to smaller objects that have their own class (ScoreHUD, Castle, etc).
Honestly you are just over thinking the button thing. Think about the touch events. touchesBegan fires when the object is clicked if you want it to fire on finger up call touchesEnded
Here is a very simple button class I wrote for this example, there are a lot of things more you could do with this, but this covers the basics. It decides if the button will click on down or up and will not click if you move your finger off the button. It uses protocols to pass the click event back to the parent (probably you scene)
you can also add this to an sks file by dragging a colorSprite/ or image onto the scene editor and making it a custom class of "Button" in the Custom Class Inspector.
the Button Class...
protocol ButtonDelegate: class {
func buttonClicked(button: Button)
}
class Button: SKSpriteNode {
enum TouchType: Int {
class down, up
}
weak var buttonDelegate: ButtonDelegate!
private var type: TouchType = .down
init(texture: SKTexture, type: TouchType) {
var size = texture.size()
super.init(texture: texture ,color: .clear, size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isPressed = true
if type == .down {
self.buttonDelegate.buttonClicked(button: self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if !frame.contains(touchLocation) {
isPressed = false
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard isPressed else { return }
if type == .up {
self.buttonDelegate.buttonClicked(button: self)
}
isPressed = false
}
}
In your GameScene file
class GameScene: SKScene, Button {
private var someButton: Button!
private var anotherButton: Button!
func createButtons() {
someButton = Button(texture: SKTexture(imageNamed: "blueButton"), type: .down)
someButton.position = CGPoint(x: 100, y: 100)
someButton.Position = 1
someButton.name = "blueButton"
addChild(someButton)
anotherButton = Button(texture: SKTexture(imageNamed: "redButton"), type: .up)
anotherButton = CGPoint(x: 300, y: 100)
anotherButton = 1
anotherButton = "redButton"
addChild(anotherButton)
}
func buttonClicked(button: Button) {
print("button clicked named \(button.name!)")
}
}
I'm subclassing nodes to use for touch detection. I have a box parent which has a line child directly next to it, with the gray space being just empty space:
The problem is when I click the gray space, it registers as a touch on the box, which is quite far away.
Here's the code where I show the problem, and my crappy workaround... I make two sets of boxes, the first being the one showing the problem, and the second being the one with the workaround:
import SpriteKit
class GameScene: SKScene {
enum sizez {
static let
box = CGSize(width: 50, height: 35),
line = CGSize(width: 200, height: 10)
}
class Box: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched box")
}
}
class Line: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched line")
}
}
override func didMove(to view: SKView) {
// The problem sprites that register touch despite empty space:
let box = Box(color: .red, size: sizez.box)
box.isUserInteractionEnabled = true
addChild(box)
let line = Line(color: .purple, size: sizez.line)
line.isUserInteractionEnabled = true
line.anchorPoint = CGPoint.zero
line.position = CGPoint(x: box.frame.maxX + 10, y: box.frame.minY)
///////////////////
box.addChild(line)
///////////////////
// These sprites detect touches properly (no detection in empty space)
let box2 = Box(color: .red, size: sizez.box)
box2.isUserInteractionEnabled = true
box2.position.y -= 100
addChild(box2)
let line2 = Line(color: .purple, size: sizez.line)
line2.isUserInteractionEnabled = true
line2.anchorPoint = CGPoint.zero
line2.position = CGPoint(x: box2.frame.maxX + 10, y: box2.frame.minY)
////////////////
addChild(line2)
////////////////
}
}
When you click directly above (or even farther out from) the top line, you get this:
When you do the same for the bottom line, you get this:
It will be a huge hassle to forgo SK's built in parent / child system, and then for me to keep track of them on my own manually... as well as it being a big performance hit for my app.
Any reasonable workaround or solution that lets me click in the gray space while using code similar to the first box would be greatly appreciated.
UPDATE:
By making an invisible background node and setting its zPositon 1 less, I can now click in the gray space and it registers as the background node, not the box.
let bkgSize = CGSize(width: 1000, height: 1000)
let bkg = Bkg(color: .gray, size: bkgSize)
bkg.isUserInteractionEnabled = true
bkg.zPosition -= 1
addChild(bkg)
But still, why is this empty space touch being registered as a box touch in the absence of a background node?
You're absolutely correct with a background node it works as expected, without a background sprite it gives the results as described by #Confused. I was able to get it to work as expected without a background by just fine tuning the TouchesBegan function like so...
class Box: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if frame.contains(touchLocation) {
print("touched box")
}
}
}
}
class Line: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if frame.contains(touchLocation) {
print("touched line")
}
}
}
}
From my understanding of how bounds work for parented objects, I think this is what's going on...
Addendum
This is slightly related, and may help in understanding why this is happening, and why Apple considers this to be how a parent and its children should use an accumulated (combined) rectangle/quad for touch response:
I am creating a game, where I have created a Simple UI, and I want the play button to be an action whenever the player hit the play button.
Here is an image:
Here is my code as well.
//I JUST DID THIS.
let bgImage = SKSpriteNode(imageNamed: "bg.png")
bgImage.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
bgImage.size = self.frame.size
bgImage.name = "button1"
self.addChild(bgImage)
By default isUserInteractionEnabled is false so the touch on a scene child like your bgImage is, by default, a simple touch handled to the main (or parent) class (the object is here, exist but if you don't implement any action, you simply touch it)
If you set the userInteractionEnabled property to true on a SKSpriteNode subclass then the touch delegates will called inside this specific class. So, you can handle the touch for the sprite within its class. But you don't need it, this is not your case, you don't have subclassed your bgImage.
You should simply made in your scene:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "button1" {
print("Tapped")
}
}
}
When I look to your image I suspected that your sprite bg.png was composed by background and also the button image: this is very uncomfortable, you should use only an image about your button and , if you want , make another sprite to show your background otherwise you touch ALL (background and button obviusly, not only the button as you needed..).
So, you should separate the image, for example your button could be this:
Did you try to add: bgImage.isUserInteractionEnabled = true ?
let bgImage = SKSpriteNode(imageNamed: "bg.png")
bgImage.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
bgImage.size = self.frame.size
bgImage.name = "button1"
bgImage.isUserInteractionEnabled = true
self.addChild(bgImage)
Then:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "button1" {
print("Tapped")
}
}
}
I find a similar question, but I am trying to detect and identify which Sprite the user touch, and I don't know how to do that. This is my variable:
var sprites: [[SKSpriteNode]] = [[SKSpriteNode(imageNamed: "a"), SKSpriteNode(imageNamed: "b")], [SKSpriteNode(imageNamed: "c"),SKSpriteNode(imageNamed: "d")]]
The idea is identify the spriteNode and then replace it for other sprite or change the color, but I don´t know how to do it with this matrix of spriteNodes, I guess the first step it´s identify the sprite.
What you are trying to do (even if I don't see a reason for this) can be accomplished using delegation pattern. Basically, you will tell your delegate (the scene, or whatever you set as a delegate) to do something for you, and you will do that directly from within the button's touchesBegan method. Also, you will pass the button's name to a scene.
To make this happen, first you have to define a protocol called ButtonDelegate. That protocol defines a requirement which states that any conforming class has to implement a method called printButtonsName(_:):
protocol ButtonDelegate:class {
func printButtonsName(name:String?)
}
This is the method which will be implemented in your GameSceneclass, but called from within button's touchesBegan. Also, this method will be used to pass a button's name to its delegate (scene), so you will always know which button is tapped.
Next thing is button class itself. Button might look like this:
class Button : SKSpriteNode{
weak var delegate:ButtonDelegate?
init(name:String){
super.init(texture: nil, color: .purpleColor(), size: CGSize(width: 50, height: 50))
self.name = name
self.userInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
delegate?.printButtonsName(self.name)
}
}
The important thing here is userInteractionEnabled = true, which means that button will accept touches. Another important thing is a delegate property. As already mentioned, buttons will have the scene set as their delegate. Setting a scene as delegate of buttons will be done later when we create some buttons... To make this easier for you, think of a delegate as a worker who works for his boss :) The boss (a button) tells his worker (a scene) to do something for him (to prints his name).
Okay, so lets make sure that scene conforms to a ButtonDelegate protocol...Why is this important? It is important because the worker (scene) must follow the orders of his boss (a button). By conforming to this protocol, the worker is making a contract with his boss where confirming that he knows how to do his job and will follow his orders :)
class GameScene: SKScene, ButtonDelegate {
override func didMoveToView(view: SKView) {
let play = Button(name:"play")
play.delegate = self
let stop = Button(name:"stop")
stop.delegate = self
play.position = CGPoint(x: frame.midX - 50.0, y: frame.midY)
stop.position = CGPoint(x: frame.midX + 50.0, y: frame.midY)
addChild(play)
addChild(stop)
}
func printButtonsName(name: String?) {
if let buttonName = name {
print("Pressed button : \(buttonName) ")
}
//Use switch here to take appropriate actions based on button's name (if you like)
}
}
And that's it. When you tap the play button, the touchesBegan on a button itself will be called, then the button will tell its delegate to print its name using the method defined inside of scene class.
First, you need another way to create the sprite, here are a way:
let spriteA = SKSpriteNode(imageNamed: "a")
scene.addChild(spriteA)
let spriteB = SKSPriteNode(imageNamed: "b")
scene.addChild(spriteB)
...and so on...
Now we needs to set a name for the sprite so we can know which node is tapped later. To add a name for a sprite just do this:
spriteNode.name = "name of the sprite"
Putting this code in the above example will look something like this:
let spriteA = SKSpriteNode(imageNamed: "a")
spriteA.name = "a"
scene.addChild(spriteA)
let spriteB = SKSPriteNode(imageNamed: "b")
spriteB.name = "b"
scene.addChild(spriteB)
...and so on...
To detect touches put this into your SKScene subclass:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let touchLocation = touch.locationInNode(self)
let targetNode = nodeAtPoint(touchLocation) as! SKSpriteNode
}
The targetNode is the node you tapped.
If you wants to get the name of the sprite you can use targetNode.name.
I am new in ios developement.i need to show scratch-card effect for an iPhone app after scratches if coupon number is visible i need to show alert message How can i do this?.i have download sample code from iPhone - Scratch and Win Example and also i have done showing below screen and scratches also works fine
if UILabel text is visible i want to show alert message How can i do this?
i have attached screen shot for your refference
i Found very nice Github example please take a look this and impliment as par you need hope this helps to you my Frnd.
CGScratch This is same thing that you want to apply.
review the code and check the visible area of Number else check che scration overImage is tatally removed of not if remove then show Alert.
A simple UIImageView subclass that allows your UIImageView become a scratch card.
In your storyboard or xib set custom class of your UIImageView that represents your scratch image to ScratchCardImageView. Change lineType or lineWidth to change the appearance of the scratch lines.
Download example
Swift 3:
import UIKit
class ScratchCardImageView: UIImageView {
private var lastPoint: CGPoint?
var lineType: CGLineCap = .square
var lineWidth: CGFloat = 20.0
override func awakeFromNib() {
super.awakeFromNib()
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
lastPoint = touch.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, let point = lastPoint else {
return
}
let currentLocation = touch.location(in: self)
eraseBetween(fromPoint: point, currentPoint: currentLocation)
lastPoint = currentLocation
}
func eraseBetween(fromPoint: CGPoint, currentPoint: CGPoint) {
UIGraphicsBeginImageContext(self.frame.size)
image?.draw(in: self.bounds)
let path = CGMutablePath()
path.move(to: fromPoint)
path.addLine(to: currentPoint)
let context = UIGraphicsGetCurrentContext()!
context.setShouldAntialias(true)
context.setLineCap(lineType)
context.setLineWidth(lineWidth)
context.setBlendMode(.clear)
context.addPath(path)
context.strokePath()
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
Updated
With this solution touch events will be tracked only inside the UIImageView bounds. If you need touch events to start already outside your scratchcard, see ScratchCardTouchContainer example
You can also check this github tutorial for scratch:
https://github.com/SebastienThiebaud/STScratchView
https://github.com/moqod/iOS-Scratch-n-See