Determine Node Touched in Gesture Recognizer - sprite-kit

I have a SpriteKit scene that can have thousands of distinct nodes in it. I am also implementing a single-tap gesture recognizer on the scene, in the hopes that I can determine which node has been touched in the scene once the gesture recognizer is triggered. Currently, my (non-working) code looks like this:
#objc func singleTap(_ sender: UIPinchGestureRecognizer) {
print("single tap gesture recognized")
if sender.numberOfTouches == 1 {
let touchPoint = sender.location(in: self.view)
let touchedNode = self.atPoint(touchPoint)
if let name = touchedNode.name
{
if name == "newMapButton"
{
print("newMapButton Touched")
} else {
print("what did you touch?")
}
}
}
}
The gesture recognizer is working. When I touch the new map button I get the "single tap gesture recognized" in the console, but nothing more. What am I doing wrong here?

In GameScene file, I created my button in didMove method like so
let btnTest = SKSpriteNode(imageNamed: "button")
btnTest.setScale(0.2)
btnTest.name = "Button"
btnTest.zPosition = 10
btnTest.position = CGPoint(x: 100, y: 200)
self.addChild(btnTest)
Adding Gesture in didMove:
let tapRec = UITapGestureRecognizer()
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
Finally implementing tappedView method
#objc func tappedView(_ sender:UITapGestureRecognizer) {
if sender.state == .ended {
var post = sender.location(in: sender.view)
post = self.convertPoint(fromView: post)
let touchNode = self.atPoint(post)
if let name = touchNode.name
{
if name == "Button"
{
print("newMapButton Touched")
} else {
print("what did you touch?")
}
}
}
}

Related

How do I zoom the view when the player zooms in or out?

So I just learned how to make a view zoom in and out, and I did it for my gamescene but theres just a black box right outside the view, like this
enter image description here
Here's my code that I used to allow zooming in and out, this is in my didMove function
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:)))
view.addGestureRecognizer(pinch)
and this is inside no function, just the GameScene class
#objc func handlePinch(sender: UIPinchGestureRecognizer) {
guard sender.view != nil else { return }
if sender.state == .began || sender.state == .changed {
sender.view?.transform = (sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale))!
sender.scale = 1.0
}
}
How would I change the view whenever someone zooms in or out?

How can I reset the position of my marker view when the cancel button is pressed?

I have a markerView which you can grab and move around. I added a panGesture to it.
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handleDraggedView(_:)))
fanMenu.addGestureRecognizer(panGesture)
To drag the view around and update the position of it:
#objc func handleDraggedView(_ sender: UIPanGestureRecognizer){
if let marker = documentationMarkers.first(where: {$0.documentationId == documentationPressed.id}) {
if(fanMenu.isOpen && marker.documentationType == "DEFAULT") {
self.viewForTilingView.bringSubview(toFront: marker)
let translation = sender.translation(in: self.viewForTilingView)
marker.center = CGPoint(x: marker.center.x + translation.x, y: marker.center.y + translation.y)
let point = viewForTilingView.convert(marker.center, to: scrollView)
fanMenu.center = CGPoint(x: point.x,
y: point.y)
sender.setTranslation(CGPoint.zero, in: self.viewForTilingView.superview)
print("Startpoint-X: \(point.x)", "Startpoint-Y: \(point.y)")
if(sender.state == .ended) {
print("Endpoint-X: \(point.x)", "Endpoint-Y: \(point.y)")
if let documentation = documentations.first(where: {$0.id == marker.documentationId}) {
let levelZoom : CGFloat = pow(2.0, CGFloat(viewForTilingView.tileProvider!.levelsOfDetail - 1))
let positionX = Int(marker.center.x * levelZoom)
let positionY = Int(marker.center.y * levelZoom)
_ = updatePosition(documentation: documentation, positionX: positionX, positionY: positionY)
documentationAdded(doc: documentation, update: true)
}
}
}
}
}
my start and endpoints are correct. When I long press my marker it prints the correct startpoints and when I release it somewhere else it gives me the correct endpoints.
I added some buttons to my marker view. Two of them are a cancel and confirm Button.
In a switch case statement it checks which button is selected. If the conform button is selected and you moved the marker around it updates the position ( = do nothing cause it updates it anyways) but when the cancel button is selected it should not update the postion and reset it to the old one.
func contextMenuButtonClicked(buttonId: String) {
let doc = documentationPressed
switch buttonId {
case "main":
break
case "cancelButton":
//update to the old position
My question is how can I get the old position of my marker view and place it to the start position when the user presses the cancel button.
You can store the marker position by switching between pan states.
For e.g.
//declare a variable inside your class- var olPostion = CGPoint()
if sender.state == .began {
oldPosition = marker.center
}
and on cancelButtonPress, reset the button position to old position
marker.center = oldPosition
NOTE: If you want to reset the button position the position it was
when the view controller loaded, assign the value od oldPosition in
viewDidLoad()

I want to limit the number of taps

Each tap adds an object on my scene, but I want to add it only once and then disable the Tap gesture. I looked everywhere but none of them are working with my code. Can someone help me with this? Either limiting the tap to 1 only or disabling it. I tried adding the tap gesture as Outlet and then setting .isEnabled = false but it is still not working.
class ARScene: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var sceneView: ARSCNView!
var tap : UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
let configuration = ARWorldTrackingConfiguration()
self.sceneView.session.run(configuration)
// Set the view's delegate
sceneView.delegate = self
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
// let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
//sceneView.scene = scene
registerGestureRecognizer()
}
func registerGestureRecognizer(){
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
#objc func handleTap(gestureRecognizer: UIGestureRecognizer){
//let touchLocation = gestureRecognizer.location(in: sceneView)
let sceneLocation = gestureRecognizer.view as! ARSCNView
let touchLocation = gestureRecognizer.location(in: sceneLocation)
let hitResult = self.sceneView.hitTest(touchLocation, types: [.existingPlaneUsingExtent, .estimatedHorizontalPlane])
if hitResult.count > 0 {
guard let hitTestResult = hitResult.first else{
return
}
let node = SCNNode()
let scene = SCNScene(named: "art.scnassets/bucket/Bucket2.scn")
let nodeArray = scene!.rootNode.childNodes
for childNode in nodeArray{
node.addChildNode(childNode as SCNNode)
}
let worldPos = hitTestResult.worldTransform
node.scale = SCNVector3Make(0.009,0.009,0.009);
node.position = SCNVector3(x: 0, y: worldPos.columns.3.y, z: -1.4)
sceneView.scene.rootNode.addChildNode(node)
tap.isEnabled = false //NOT WORKING, I want to stop tap gesture here
}
}
In order to disable your tapGesture you need to have reference to the same gesture instance you assign tap to.
There are two ways either create global instance so you can change its properties like Enable/Disable from anywhere
Or access the variable from the action/method for that gesture.
Create the global varibale for your tap
like
var tap: UITapGestureRecognizer! // global varibale
func registerGestureRecognizer(){
tap = UITapGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
and then disable this in taphandle
tap.isEnabled = false // disable the gesture
Or
2 Update your handle methods to
#objc func handleTap(withTapRecognizer tapGesture: UITapGestureRecognizer) {
........
tapGesture.isEnabled = false // use the instance variable and disable the gesture this will disable the gesture from which its been called
}
For you to access the gesture in the function, you need to initialize it using
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:)))
Then in your handleTap method, you can this at the end of the function
gestureRecognizer.view?.removeGestureRecognizer(gestureRecognizer)

Conflict between Pan Gesture and Tap Gestures

I'm currently working on a game that uses UIGestureRecognizers. I'm using the pan gesture to move the player around and the tap gesture to detect other UI button touches. Everything seems to work fine except that there is a conflict between the 2 gestures. Whenever the player is on the move (pan gesture gets recognized) the game ignores all my tap gestures (Once the pan gesture is recognized, the view won't recognize tap gestures).
Can someone please show me how to make the 2 gestures work together. I want the player to stop moving when a UI button gets tapped. In another word, I want to cancel the pan gesture whenever a tap gesture is recognized.
Thank you so much in advance!
Here is how I setup the 2 gestures:
let singleTap = UITapGestureRecognizer(target: self, action: #selector(doSingleTap))
singleTap.numberOfTapsRequired = 1
singleTap.delegate = self
self.view?.addGestureRecognizer(singleTap)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGesture.minimumNumberOfTouches = 1
panGesture.delegate = self
self.view?.addGestureRecognizer(panGesture)
#objc func handlePan(gestureReconizer: UIPanGestureRecognizer) {
if isPaused || player.isInAction {return}
let translation = gestureReconizer.translation(in: self.view)
if gestureReconizer.state == .changed {
let angle = 180 + (atan2(-translation.x, translation.y) * (180/3.14159))
player.movementAngle = angle
player.atState = .Walk
}
if gestureReconizer.state == .ended {
player.movementAngle = 0.0
if player.atState == .Walk {
player.atState = .Idle
}
}
}
#objc func doSingleTap(gestureReconizer: UITapGestureRecognizer) {
let originaTapLocation = gestureReconizer.location(in: self.view)
let location = convertPoint(fromView: originaTapLocation)
let node = atPoint(location)
switch node.name {
case "HeroAvatar":
//do stuff here
break
case "Fire":
//do stuff here
break
case "Potion":
//do stuff here
break
default:
break
}
}
You need to implement delegate method of UIGestureRecognizerDelegate like below:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Don't recognize a single tap gesture until a pan gesture fails.
if gestureRecognizer == singleTap &&
otherGestureRecognizer == panGesture {
return true
}
return false
}
Hope this will work for you :)
For more info Apple Doc Preferring One Gesture Over Another

Swift 3 Draggable UIButton

Apologies as I'm still learning the basics of Swift.
I'm trying to move a button when I drag it which sounds simple. I can't figure out how to pass along the sender information to the drag function so I can associate it with the button that is being dragged.
I create multiple one word buttons which are only text and attach a pan gesture recognizer to each of them:
let pan = UIPanGestureRecognizer(target: self, action: #selector(panButton(_:)))
let word = UIButton(type: .system)
word.addGestureRecognizer(pan)
I've created this function to trigger when the button is moved:
func panButton(sender: UIPanGestureRecognizer){
if sender.state == .began {
//wordButtonCenter = button.center // store old button center
} else if sender.state == .ended || sender.state == .failed || sender.state == .cancelled {
//button.center = wordButtonCenter // restore button center
} else {
let location = sender.location(in: view) // get pan location
//button.center = location // set button to where finger is
}
}
I'm getting the following error:
Use of unresolved identifier 'panButton'
First of all, your action needs to be a selector in Swift 3. So that would look something like this:
let pan = UIPanGestureRecognizer(target: self, action: #selector(panButton(_:))
Also, you can't pass the value of a button through a selector, so you would need to change your func to be:
func panButton(sender: UIPanGestureRecognizer){
...
}
If you're wondering how you are supposed to find the button if you can't pass it as a parameter, then you might consider using tags.
As #benjamin points out in Swift 3 you needs to be a selector. I've updated my code to the following in order to extract the button tag:
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(panGesture:)))
panGesture.minimumNumberOfTouches = 1
let word = UIButton(type: .system)
With the following selector:
#objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {
let buttonTag = (recognizer.view?.tag)!
if let button = view.viewWithTag(buttonTag) as? UIButton {
if recognizer.state == .began {
wordButtonCenter = button.center // store old button center
} else if recognizer.state == .ended || recognizer.state == .failed || recognizer.state == .cancelled {
button.center = wordButtonCenter // restore button center
} else {
let location = recognizer.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
}