SKScene nodes not detecting touch - ios

I am trying to make an ARKit app for ios and the nodes in the scene are not responding to touch. The scene is properly displayed but I haven't been able to detect any touch.
fileNamed: "TestScene" refers to a TestScene.sks file in my project which is empty and I add the node in the code as shown below.
let detailPlane = SCNPlane(width: xOffset, height: xOffset * 1.4)
let testScene = SKScene(fileNamed: "TestScene")
testScene?.isUserInteractionEnabled = true
let winner = TouchableNode(fontNamed: "Chalkduster")
winner.text = "You Win!"
winner.fontSize = 65
winner.fontColor = SKColor.green
winner.position = CGPoint(x: 0, y: 0)
testScene?.addChild(winner)
let material = SCNMaterial()
material.diffuse.contents = testScene
material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
detailPlane.materials = [material]
let node = SCNNode(geometry: detailPlane)
rootNode.addChildNode(node)
For TouchableNode I have the following class
class TouchableNode : SKLabelNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touch detected")
}
}

I've achieved this affect using gesture recognize
private func registerGestureRecognizers() -> Void {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
sceneView.addGestureRecognizer(tapGestureRecognizer)
}
then have a function to handle the tap gesture
#objc private func handleTap(sender: UITapGestureRecognizer) -> Void {
let sceneViewTappedOn = sender.view as! SCNView
let touchCoordinates = sender.location(in: sceneViewTappedOn)
let hitTest = sceneViewTappedOn.hitTest(touchCoordinates)
if !hitTest.isEmpty {
let hitResults = hitTest.first!
var hitNode = hitResults.node
// do something with the node that has been tapped
}
}
}

You need to do isUserInteractionEnabled = true first.
So, something like:
class TouchableNode : SKLabelNode {
override init() {
super.init()
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touch detected")
}
}

Related

How to drag certain image in iOS?

Wanted to know how I can drag a image across screen and what code would be used. Tried looking up but only older versions of Swift have answer and no longer work. I want to drag the image, but not place finger on screen and it goes to that spot. Just drag.
Gives me the error:
"Use of undeclared type 'uitouch'"
import UIKit
class DraggableImage: UIImageView {
override func touchesMoved(touches: Set<uitouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let position = touch.locationInView(superview)
center = CGPointMake(position.x, position.y)
}
}
}
You need to subclass UIImageView and in the init you need to set userInteractionEnabled = true and then override this method override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) well, my code is this:
class DraggableImage: UIImageView {
var localTouchPosition : CGPoint?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.red.cgColor
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
self.localTouchPosition = touch?.preciseLocation(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
return
}
self.frame.origin = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.localTouchPosition = nil
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
}
This is how it looks
Hope this helps
Create a Nsobject Class for moving View and add following Code
import UIKit
class objectClass: UIImageView, UIGestureRecognizerDelegate {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
self.center = touch.location(in: self.superview)
}
}
in mainViewController make a object of NSobject class
var newView: objectClass = objectClass()
on button Action to add new View
#IBAction func objectAdded(theButton: UIButton!) {
let frame = CGRect(x: 100, y: 100, width: 44, height: 44)
newView = objectClass(frame: frame)
if theButton.titleLabel?.text == "image1" {
newView.image = UIImage(named: "1")
} else if theButton.titleLabel?.text == "image2" {
newView.image = UIImage(named: "2")
}else{
newView.image = UIImage(named: "3")
}
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
self.view .addSubview(newView)
newView.alpha = 0
UIView .animate(withDuration: 0.4) {
self.newView.alpha = 1
}
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseOut, animations: { () -> Void in
self.sliderViewBottomLayoutConstraint.constant = self.sliderViewBottomLayoutConstraint.constant - self.sliderViewBottomLayoutConstraint.constant
self.view.layoutIfNeeded()
}, completion: nil)
image1Button.isEnabled = false
image2Button.isEnabled = false
image3Button.isEnabled = false
let pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.recognizePinchGesture(sender:)))
pinchGesture.delegate = self
let rotateGesture: UIRotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(ViewController.recognizeRotateGesture(sender:)))
let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.RemoveSelectedImageOnTap(sender:)))
tapGesture.numberOfTapsRequired = 2
self.newView.addGestureRecognizer(tapGesture)
self.newView.addGestureRecognizer(pinchGesture)
self.newView.addGestureRecognizer(rotateGesture)
}
func recognizePinchGesture(sender: UIPinchGestureRecognizer) {
sender.view!.transform = sender.view!.transform.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
}
func recognizeRotateGesture(sender: UIRotationGestureRecognizer) {
sender.view!.transform = sender.view!.transform.rotated(by: sender.rotation)
sender.rotation = 0
}

How to recognise which image was touched

I am developing an application which the user will be able to drag and drop items on a canvas and when he releases the image it is drawn on the canvas.
This is my DragImage class which handle the touches:
class DragImages: UIImageView {
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder : NSCoder){
super.init(coder : aDecoder)
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget{
let position = touch.location(in: self.superview)
if target.frame.contains(position){
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: nil))
}else {
self.center = originalPos
}
}
print(self.center.x, self.center.y)
self.center = originalPos
}
func getEndPosX() -> CGFloat{
return self.center.x
}
func getEndPosY() -> CGFloat {
return self.center.y
}
}
In my ViewController class I added this piece of code to handle the touches etc:
ornament1.dropTarget = xmasTree
ornament2.dropTarget = xmasTree
ornament3.dropTarget = xmasTree
ornament4.dropTarget = xmasTree
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.itemDroppedOnTree(_:)), name: NSNotification.Name(rawValue: "onTargetDropped"), object: nil)
}
func itemDroppedOnTree(_ notif : AnyObject){
}
I managed to get the X and Y position when the image is dragged on the canvas but i cant find a way to recognise which of the 4 images is being dropped in order for me to draw that specific one!
You could add the sender to your notification (and also the position):
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: self, userInfo: ["position":position]))
and get it later in itemDroppedOnTree:
func itemDroppedOnTree(_ notif : NSNotification){
let position = notif.userInfo["position"]
let sender = notif.object as! DragImage
if sender === dragImage1 {
//...
} else if sender === dragImage2 {
//...
}
}
I recommend against it though and plead to use a delegate to inform the ViewController instead. (Opinion based: In general, use Notifications for to-many broadcasts only.)
The delegate function should have the sender as first parameter. According to func tableView: tableView:UITableView, cellForRowAt indexPath:IndexPath).
This way you know which image is sending its new position and can compare it to your property like in the above example:
if dragImage === dragImage1 {...
Your code plus working delegate to paste to Playground:
import UIKit
import PlaygroundSupport
protocol DragImageDelegate: class {
func dragimage(_ dragImage:DragImage, didDropAt position:CGPoint)
}
class DragImage: UIImageView {
weak var delegate: DragImageDelegate?
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget {
let position = touch.location(in: self.superview)
if target.frame.contains(position){
print(self.center.x, self.center.y)
guard let delegate = self.delegate else {
print("delegate not set")
return
}
print(self.center.x, self.center.y)
delegate.dragimage(self, didDropAt: position)
return
}
}
self.center = originalPos
}
}
class MyVC: UIViewController, DragImageDelegate {
let dragImage1 = DragImage(frame: CGRect(x: 0.0, y: 0.0, width: 30.0, height: 30.0))
let dragImage2 = DragImage(frame: CGRect(x: 0.0, y: 100.0, width: 30.0, height: 30.0))
override func viewDidLoad() {
let target = UIView(frame: CGRect(x: 200.0, y: 400.0, width: 30.0, height: 30.0))
target.backgroundColor = .black
view.addSubview(target)
dragImage1.backgroundColor = .white
dragImage2.backgroundColor = .white
dragImage1.dropTarget = target
dragImage2.dropTarget = target
view.addSubview(dragImage1)
view.addSubview(dragImage2)
dragImage1.delegate = self
dragImage2.delegate = self
}
private func move(_ view:UIView, to position:CGPoint) {
view.frame = CGRect(x: position.x, y: position.y, width: view.frame.size.width, height: view.frame.size.height)
}
// MARK: - DragImageDelegate
func dragimage(_ dragImage: DragImage, didDropAt position: CGPoint) {
if dragImage === dragImage1 {
move(dragImage1, to: position)
} else if dragImage === dragImage2 {
move(dragImage2, to: position)
}
}
}
var container = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0))
let myVc = MyVC()
myVc.view.frame = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0)
myVc.view.backgroundColor = .green
container.addSubview(myVc.view)
PlaygroundPage.current.liveView = container
Result:

How to subclass a SKSpriteNode and implement touchesBegan?

I'm trying to create a node that has the ability to be swiped offscreen. I tried adding a UIGestureRecognizer the the view but the swipe only works for the first node. I heard there is a way to subclass a spriteNode and in the touchesBegan add the ability to swipe off screen. I can setup a base for a custom sprite node but am unsure of how to give it the ability to be swiped. Here is my code for a custom sprite node:
class CustomNode: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: customNodeImage)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I do have a touchesBegan in my scene but when I touch a node I get a "nil" response when I try and print out the node's name. Here is my scene code:
import SpriteKit
let plankName = "woodPlank"
class PlankScene: SKScene {
var plankWood : Plank?
var plankArray : [SKSpriteNode] = []
override func didMove(to view: SKView) {
plankWood = childNode(withName: "woodPlank") as? Plank
plankWood?.isUserInteractionEnabled = false
let swipeRight : UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(PlankScene.swipedRight))
swipeRight.direction = .right
view.addGestureRecognizer(swipeRight)
}
func swipedRight(sender: UISwipeGestureRecognizer) {
if sender.direction == .right {
let moveOffScreenRight = SKAction.moveTo(x: 400, duration: 0.5)
let nodeFinishedMoving = SKAction.removeFromParent()
let waitForNode = SKAction.wait(forDuration: 0.5)
plankWood?.run(SKAction.sequence([moveOffScreenRight, nodeFinishedMoving]),
completion:{
} )
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
print("\(plankWood?.name)")
if plankWood?.name == plankName {
print("Plank touched")
}
}
override func enumerateChildNodes(withName name: String, using block: #escaping (SKNode, UnsafeMutablePointer<ObjCBool>) -> Void) {
let swipeRight : UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(PlankScene.swipedRight))
swipeRight.direction = .right
view?.addGestureRecognizer(swipeRight)
}
}

Issue with recognising touch in SKLabelNode

I have been programming in Swift for about four months now using Sprite Kit to build some simple Arcade games for IOS. Until recently I haven't had any problems with recognising touches in specific nodes. In the main screen in one of my projects, I have added another SKLabelNode for the latest addition to the app, following the same layout of implementation as the others, but this one doesn't work. When the label is tapped it is supposed run a function but doesn't even get that far, I figured out using breakpoints. Here is all of the relevant code, please have a look, I have going over this four hours and it has been driving me crazy.
import SpriteKit
let twistedLabelName = "twisted"
class StartScene: SKScene {
var play1P = SKLabelNode(fontNamed: "HelveticaNeue-Thin")
var play2P = SKLabelNode(fontNamed: "HelveticaNeue-Thin")
var playTwisted = SKLabelNode(fontNamed: "HelveticaNeue-Thin")
override func didMoveToView(view: SKView) {
initializeValues()
self.userInteractionEnabled = true
}
func initializeValues () {
backgroundColor = UIColor.whiteColor()
play1P.name = "1p"
play1P.text = "Play 1P"
play1P.fontColor = SKColor.blackColor()
play1P.fontSize = 40
play1P.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetHeight(self.frame) * 0.8)
play1P.zPosition = 100
self.addChild(play1P)
play2P.name = "2p"
play2P.text = "Play 2P"
play2P.fontColor = SKColor.blackColor()
play2P.fontSize = 40
play2P.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetHeight(self.frame) * 0.6)
play2P.zPosition = 100
self.addChild(play2P)
playTwisted.text = "Twisted"
playTwisted.fontColor = SKColor.blackColor()
playTwisted.name = twistedLabelName
playTwisted.fontSize = 40
playTwisted.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetHeight(self.frame) * 0.4)
self.addChild(playTwisted)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "1p" {
// Some function
}
else if theName == "2p" {
// Some function
}
else if theName == twistedLabelName {
// This is the one that doesn't work
// Some function
}
}
}
}
}
Note that you can extend any SKNode (including SKLabelNode) and handle touches in the subclass. Set isUserInteractionEnabled = true first though. e.g.
import SpriteKit
class MyLabelNode: SKLabelNode {
override init() {
super.init()
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print( #file + #function )
}
}

Detecting when a user taps a SKSpriteNode

I'm new to swift programming and I decided I would make a simple game to start with SpriteKit. I have a SpriteNode that is supposed to pick 1 of 6 locations and move there when it is tapped, however from the methods I've seen I can't figure out how to implement it (again I'm new at this) Here is my code from the GameScene.swift file:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let screenSize: CGRect = UIScreen.mainScreen().bounds
let greenTileWidth = screenSize.width * 0.5
let greenTileHeight = screenSize.height * 0.33
let greenTilePositionY = [greenTileHeight / 2, greenTileHeight / 2 + greenTileHeight, greenTileHeight / 2 + greenTileHeight * 2 ]
let greenTilePositionX = [greenTileWidth / 2, greenTileWidth / 2 + greenTileWidth]
let backgroundTile = SKSpriteNode(imageNamed: "whiteTile")
backgroundTile.size.width = screenSize.width * 100
backgroundTile.size.height = screenSize.height * 100
addChild(backgroundTile)
let greenTile = SKSpriteNode(imageNamed: "greenTile")
greenTile.size.width = greenTileWidth
greenTile.size.height = greenTileHeight
greenTile.position.y = greenTilePositionY[0]
greenTile.position.x = greenTilePositionX[0]
greenTile.userInteractionEnabled = true
addChild(greenTile)
var randomX:Int = 0
var randomY:Int = 0
func getRandomY() -> Int{
randomY = Int(arc4random_uniform(26))%3
return randomY
}
func getRandomX() -> Int{
randomX = Int(arc4random_uniform(26))%2
return randomX
}
func moveGreenTile(){
greenTile.position.x = greenTilePositionX[randomX]
greenTile.position.y = greenTilePositionY[randomY]
}
getRandomX()
getRandomY()
moveGreenTile()
}
when the SpriteNode greenTile is tapped, getRandomY() getRandomX() and moveGreenTile() should be called.
First you have to set the name attribute of your SKSpriteNodes:
greenTile.name = "greenTile"
First I see some errors in your code. The return values of getRandomX and getRandomY never get really used. Because you set the randomX and randomY variables without actually calling getRandom. So you should update it to:
func moveGreenTile(){
greenTile.position.x = greenTilePositionX[getRandomX()]
greenTile.position.y = greenTilePositionY[getRandomY()]
}
That way you only have to call moveGreenTile and it will call the getRandom methods by itself.
Then you have to use the touchesBegan method to check if the user touches the screen. So with the name you can check if the user touched the greenTile by checking the name you've set earlier:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches{
let location = touch.locationInNode(self)
let node:SKNode = self.nodeAtPoint(location)
if(node.name == "greenTile"){
moveGreenTile()
}
}
}
This code detects tap events, not only touches, on a SKSpriteNode.
You can change how sensitive the tap gesture is by modifying TapMaxDelta.
class TapNode : SKSpriteNode {
// Tap Vars
var firstPoint : CGPoint?
var TapMaxDelta = CGFloat(10)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
let texture = SKTexture(imageNamed: "Test.png")
super.init(texture: texture, color: UIColor.clear, size: texture.size())
isUserInteractionEnabled = true
}
// ================================================================================================
// Touch Functions
// ================================================================================================
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first {
firstPoint = firstTouch.location(in: self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first, let firstPoint = firstPoint {
let curPoint = firstTouch.location(in: self)
if abs(curPoint.x - firstPoint.x) <= TapMaxDelta && abs(curPoint.y - firstPoint.y) <= TapMaxDelta {
print("tap yo")
}
}
}
}

Resources