Swift: Using switch statement in touchesBegan - ios

I want to clean up my touchesBegan(..) in SKScene. I wanted to make a case statement instead of my if .. else chain. However, I get errors when implementing, which suggests that I don't know how equality is done under the hood.
Before code:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) === playLabel {
buildLevelSelect()
} else if self.nodeAtPoint(location) === aboutLabel {
createAboutView()
} else if self.nodeAtPoint(location) === storeLabel {
createStore()
}
}
}
After code: After clicking around, some label clicks work, but some others raise a Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode 0x0) error:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
switch(self.nodeAtPoint(location)){
case playLabel :
buildLevelSelect()
case aboutLabel :
createAboutView()
case storeLabel :
createStore()
default : break
}
}

You can write a sort of odd looking but functional switch if you want the === behavior as follows:
switch(true){
case self.nodeAtPoint(location) === playLabel : buildLevelSelect()
case self.nodeAtPoint(location) === aboutLabel : createAboutView()
case self.nodeAtPoint(location) === storeLabel : createStore()
default : break
}
In my opinion, this is still cleaner looking than the if-else chain.

The easiest way to accomplish this would be by populating the .name property of your nodes, so then you could switch on the name instead. So then your switch would look like this:
switch (self.nodeAtPoint(location).name ?? "") {
case "playLabel" :
buildLevelSelect()
case "aboutLabel" :
...

You can also cast the objects in the switch statement and then your code works as expected
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: self)
switch self.atPoint(touchLocation) {
case redBox as SKSpriteNode:
print("touched redBox")
case greenBox as SKSpriteNode:
print("touched greenBox")
default:
print("touched nothing")
}
}

Related

How to detect which one object i touched in sceneView at ARKitExample?

in Apple's ARKitExample, if i add multiple virtual objects(two chair) in one scene view. How to detect which one chair i touched in sceneView at ARKitExample?
the sceneView.hitTest() function will return the array of SCNHitTestResult, but the result.node is kind class of SCNNode, i don't know the object i touched is which one chair?
Dose anyone could help this? Thanks a lots
You are respondsible for tracking which nodes belong to which objects. I usually use a Set since SCNNode is hashable. You can then easily test if the node belongs to one of the objects you are interested in:
guard let result = sceneView.hitTest(location, options: nil).first else {
return
}
if myObjectNodes.contains(result.node) { //myObjectNodes is declared as Set<SCNNode>
//This is a match
}
To elaborate on the answer from #JoshHomann, you can do something like this
Swift 4
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first as! UITouch
if(touch.view == self.sceneView){
print("touch working")
let viewTouchLocation:CGPoint = touch.location(in: sceneView)
guard let result = sceneView.hitTest(viewTouchLocation, options: nil).first else {
return
}
if myObjectNodes.contains(result.node) { //myObjectNodes is declared as Set<SCNNode>
print("match")
}
}
}

Initializer for conditional binding must have Optional type, not 'UITouch'

I am currently getting an error in this code block for a SpriteKit game. In the if let statement I am getting the following error.
Initializer for conditional binding must have Optional type, not
'UITouch'
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
StopSideMovement = false
if let touch = touches.first! as UITouch {
if touch.locationInView(self.view).x > screenWidth/2 {
MoveRight = true
} else if touch.locationInView(self.view).x < screenWidth/2 {
MoveLeft = true
}
}
}
Does anybody know how I can fix this issue? Thank you!
This line
if let touch = touches.first! as UITouch {
should be
if let touch = touches.first {
Why?
touches.first does return a UITouch?. But if you add this guy ! and write
touches.first!
then you are performing a force unwrap and you are getting (at compile time) a UITouch.
So the whole conditional unwrapping is no longer needed.
Of course you should avoid force unwrap an use safer statements (like the conditional unwrapping) unless your are absolutely sure there will be some value inside the Optional.
The type of the Set's contents are specified as being UITouch. So you don't need the downcast. you just need to unwrap it.
if let touch = touches.first {}

Set <UITouch>? does not have a member named Generator

I find it hard to fix this error please who can help me
xcode 7 beta 2
Set UITouch ? does not have a member named Generator
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
for obj in touches {
if let touch = obj as? UITouch {
let view = self.touchToView[touch]
self.handleControl(view, controlEvent: .TouchCancel)
self.touchToView[touch] = nil
}
}
}
}
enter image description here
You need to unwrap the optional - try
for obj in touches! {
// code here
}

switching between enum states with touch

I have a simple player class like this:
enum State: Int {
case dead, alive, idle
}
class Player {
var playerSprite : SKSpriteNode
var currentState : State
init(passedInState: State) {
currentState = passedInState
self.playerSprite = SKSpriteNode(imageNamed: "playersprite.png")
switch currentState {
case .dead:
println("Player is dead")
case .alive:
println("Player is alive")
}
}
}
In my main class i create an instance of the Player class and try to change the players state by tapping on it in the touch method like so:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let player1 = Player(passedInState: .alive)
self.addChild(player1.playerSprite)
player1.playerSprite.position = CGPointMake(100, 100)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
var locationSprites = nodesAtPoint(location)
for bla in locationSprites {
(bla as Player).currentState = .dead
}
}
}
I always get a crash when touching the Sprite. Do you have any idea why?
It seems there's a downcast failure, and in your code I see there are 2 forced donwcasts:
let touch = touches.anyObject() as UITouch
and
(bla as Player).currentState = .dead
I presume the error is in the last one, so I suggest setting a breakpoint and inspecting bla to check whether it's actually an instance of Player. Basing on your logic, if it can happen that it is not an instance of Player, I recommend using optional downcast:
if let player = bla as? Player {
player.currentState = .dead
}
The advantage is that if bla is not an instance of Player, the if block won't be executed, instead of making the app crash.
Comment follow up
If you want to do some custom processing when the player status changes, you can add a property observer to the currentState property of the Player class:
var currentState : State {
didSet {
switch currentState {
case .dead:
println("Player is dead")
case .alive:
println("Player is alive")
case .idle:
break
}
}
}

False if Statement Still Executes

I am using the Xcode 6 Beta, and I have used this exact code in another class, setup up in the same way. For some reason it doesn't want to work here. When I touch an SKNode, I grab its name, and compare the name to two strings, if it matches either of them, I execute some code. See below.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let node: SKNode = self.nodeAtPoint(touch.locationInNode(self))
if node.name == "body" || "face" {
self.childNodeWithName("face").runAction(SKAction.rotateByAngle(6.283, duration: 0.75))
}
}
}
As I said before, this exact code work flawlessly everywhere else I have used it, but no matter which node I press, the code inside the if statement will be run. Yes, I have printed out the names of nodes I am touching, and they do not match. Any ideas?
You probably meant
if node.name == "body" || node.name == "face" {
It would be more clear with parentheses, you are doing:
if (node.name == "body") || ("face") {
And "face" is true, which probably is a bug on Apple's side as String does not conform to LogicValue so you should not be able to test it like that. Probably the literal gets interpreted as something else conforming to LogicValue e.g. a CString.
Your if is idiomatically expressed as a switch with
switch node.name {
case "body", "face":
self.childNodeWithName ...
default: break
}
I think it should
if node.name == "body" || node.name == "face" { //change this statement
replace this in your function
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let node: SKNode = self.nodeAtPoint(touch.locationInNode(self))
if node.name == "body" || node.name == "face" { //change this statement
self.childNodeWithName("face").runAction(SKAction.rotateByAngle(6.283, duration: 0.75))
}
}
}

Resources