I created a game with a SceneKit scene into a UIViewController.
I implemented a UITapGestureRecognizer like this :
// TAP GESTURE
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap:")
sceneView.overlaySKScene?.view!.addGestureRecognizer(tapGesture)
And the handleTap function :
func handleTap(sender: UIGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Ended {
var touchLocation = sender.locationInView(sender.view)
touchLocation = self.view.convertPoint(touchLocation, toView: self.view)
let touchedNode = sceneView.overlaySKScene?.nodeAtPoint(touchLocation)
if touchedNode == myNode {
// DO SOMETHING
}
}
}
Imagine that myNode's position is (x: 150.0, y: 150.0).
The problem is that the touchPosition.y is at the opposite of the y position of myNode's y position.
How can I invert y position of the touch ?
Thanks !
Try this instead, its accurate for me:-
override func didMoveToView(view: SKView) {
let tap:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tap:"))
tap.numberOfTapsRequired = 1
view.addGestureRecognizer(tap)
}
func tap(sender:UITapGestureRecognizer){
if sender.state == .Ended {
var touchLocation: CGPoint = sender.locationInView(sender.view)
touchLocation = self.convertPointFromView(touchLocation)
}
}
Related
I calculated the speed of UIGestureRecognizer and I want to use it as velocity of Physicsbody in SpriteKit.
How can I do that since Speed is calculated in touches began and touches ended method, while my Physicsbody is wrapped in swipeUp & swipeDown etc functions, which does not have access to the Speed variable?
Here is my code:
class GameScene: SKScene, SKPhysicsContactDelegate, SKViewDelegate, UIGestureRecognizerDelegate, UIViewControllerTransitioningDelegate {
var Kite = SKSpriteNode(imageNamed: "ASC_8025_large.jpg")
#objc let minusButton = SKSpriteNode(imageNamed: "minus.jpg")
#objc let plusButton = SKSpriteNode(imageNamed: "plus.png")
//override init(Kite: SKSpriteNode) {
//For long Long press gesture to work
let longPressGestureRecPlus = UILongPressGestureRecognizer()
let longPressGestureRecMinus = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(press:)))
//First we declare all of our Gestures...
//swipes
let swipeRightRec = UISwipeGestureRecognizer()
let swipeLeftRec = UISwipeGestureRecognizer()
let swipeUpRec = UISwipeGestureRecognizer()
let swipeDownRec = UISwipeGestureRecognizer()
//rotate
let rotateRec = UIRotationGestureRecognizer()
//taps
let tapRec = UITapGestureRecognizer()
let tapRec2 = UITapGestureRecognizer()
override func didMove(to view: SKView) {
// Get label node from scene and store it for use later
Kite.size = CGSize(width: 40.0, height: 40.0)
Kite.position = CGPoint(x: frame.midX, y: frame.midY)
plusButton.size = CGSize(width: 30.0, height: 30.0)
plusButton.position = CGPoint(x: frame.midX, y: frame.minY + 20.0)
plusButton.name = "plusButton"
//plusButton.isUserInteractionEnabled = true
addChild(Kite)
addChild(plusButton)
swipeRightRec.addTarget(self, action: #selector(GameScene.swipedRight) )
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
swipeLeftRec.addTarget(self, action: #selector(GameScene.swipedLeft) )
swipeLeftRec.direction = .left
self.view!.addGestureRecognizer(swipeLeftRec)
swipeUpRec.addTarget(self, action: #selector(GameScene.swipedUp) )
swipeUpRec.direction = .up
self.view!.addGestureRecognizer(swipeUpRec)
swipeDownRec.addTarget(self, action: #selector(GameScene.swipedDown) )
swipeDownRec.direction = .down
self.view!.addGestureRecognizer(swipeDownRec)
//notice the function this calls has (_:) after it because we are passing in info about the gesture itself (the sender)
rotateRec.addTarget(self, action: #selector (GameScene.rotatedView (_:) ))
self.view!.addGestureRecognizer(rotateRec)
// again notice (_:), we'll need this to find out where the tap occurred.
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
tapRec2.addTarget(self, action:#selector(GameScene.tappedView2(_:) ))
tapRec2.numberOfTouchesRequired = 1
tapRec2.numberOfTapsRequired = 2 //2 taps instead of 1 this time
self.view!.addGestureRecognizer(tapRec2)
longPressGestureRecPlus.addTarget(self, action: #selector(longPressed(press:)))
longPressGestureRecPlus.minimumPressDuration = 2.0
self.view?.addGestureRecognizer(longPressGestureRecPlus)
}
//the functions that get called when swiping...
#objc func swipedRight() {
print("Right")
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Tilts the Kite towards Right
let tiltRight = SKAction.rotate(toAngle: -1.00, duration: 0.1)
Kite.run(tiltRight)
}
#objc func swipedLeft() {
Kite.physicsBody?.velocity = CGVector(dx: -60, dy: 60)
//Tilts the Kite towards Right
let tiltLeft = SKAction.rotate(toAngle: 1.00, duration: 0.1)
Kite.run(tiltLeft)
print("Left")
}
#objc func swipedUp() {
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Straightens the Kite
let straightens = SKAction.rotate(toAngle: 0.00, duration: 0.1)
Kite.run(straightens)
print("Up")
}
#objc func swipedDown() {
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: -60)
print("Down")
}
// what gets called when there's a single tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Single tap")
print(point)
}
// what gets called when there's a double tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView2(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Double tap")
print(point)
}
//what gets called when there's a rotation gesture
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func rotatedView(_ sender:UIRotationGestureRecognizer) {
if (sender.state == .began) {
print("rotation began")
}
if (sender.state == .changed) {
print("rotation changed")
//you could easily make any sprite's rotation equal this amount like so...
//thePlayer.zRotation = -sender.rotation
//convert rotation to degrees...
let rotateAmount = Measurement(value: Double(sender.rotation), unit: UnitAngle.radians).converted(to: .degrees).value
print("\(rotateAmount) degreess" )
}
if (sender.state == .ended) {
print("rotation ended")
}
}
func removeAllGestures(){
//if you need to remove all gesture recognizers with Swift you can do this....
for gesture in (self.view?.gestureRecognizers)! {
self.view?.removeGestureRecognizer(gesture)
}
//this is good to do before a SKScene transitions to another SKScene.
}
func removeAGesture()
{
//To remove a single gesture you can use...
self.view?.removeGestureRecognizer(swipeUpRec)
}
//Fix the gesture recognizing the plusButton
#objc func longPressed(press: UILongPressGestureRecognizer) {
if press.state == .began {
isUserInteractionEnabled = true
let positionInScene = press.location(in: self.view)
let touchedNode = self.atPoint(positionInScene)
plusButton.name = "plusButton"
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 60.0*3)
print("Pressed on the screen")
if let name = touchedNode.name {
if name == "plusButton" {
print("LONG TAPPED")
}
}
}
}
var start: CGPoint?
var startTime: TimeInterval?
var taps = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Began")
Kite.physicsBody = SKPhysicsBody(rectangleOf: Kite.size)
Kite.physicsBody?.affectedByGravity = false
//Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
Kite.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 5))
plusButton.name = "plusButton"
plusButton.isUserInteractionEnabled = false
//We figure how to interact with BUTTON in Spritekit
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "plusButton" {
taps += 1
Kite.size = CGSize(width: 200, height: 200)
print("tapped")
print("Taps", taps)
}
}
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
start = location
startTime = touch.timestamp
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Ended")
//Calculating Speed of the Gestures
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
var dx:CGFloat = location.x - start!.x;
var dy:CGFloat = location.y - start!.y;
var magnitude:CGFloat = sqrt(dx*dx+dy*dy)
//Calculate Time
var dt:CGFloat = CGFloat(touch.timestamp - startTime!)
//Speed = Distance / Time
var speed:CGFloat = magnitude / dt
var speedX:CGFloat = dx/dt
var speedY:CGFloat = dy/dt
print("SpeedY", speedX)
print("SpeedY", speedY)
}
}
so calculating the speed from a vector like dx and dy is
let speed = sqrtf(Float(pow(dx!, 2) + pow(dy!, 2)))
So if you want apply the speed as velocity to an object you need at least one of the directions.
let newDx = 0 // means in no x direction
// next you can change the formula to your new dy to match the given speed
let newDy = sqrtf(pow(speed, 2) - Float(pow(newDx, 2)))
yourNode.physicsBody?.velocity = CGVector(dx: newDx, dy: CGFloat(newDy))
I am using UIPanGestureRecognizer to rotate my sprite nodes with one finger (i.e. dragging the sprite up rotates it clockwise, and down anti clockwise).
This is my piece of code that works for the above function
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
self.view!.addGestureRecognizer(gestureRecognizer)
handlePan(gestureRecognizer)
func handlePan(_ gestureRecognizer:UIPanGestureRecognizer){
if gestureRecognizer.state == .began {
var touchLocation = gestureRecognizer.location(in: gestureRecognizer.view)
touchLocation = self.convertPoint(fromView: touchLocation)
gestureRecognizer.minimumNumberOfTouches = 1
self.selectTouchedNode(touchLocation: touchLocation)
}
if gestureRecognizer.state == .changed {
var touchLocation = gestureRecognizer.location(in: gestureRecognizer.view)
touchLocation = self.convertPoint(fromView: touchLocation)
var translation = gestureRecognizer.translation(in: gestureRecognizer.view!)
translation = CGPoint(x: translation.x, y: translation.y)
self.rotateNode(translation: translation)
}
if gestureRecognizer.state == .ended {
selectedNode.removeAllActions()
}
}
func selectTouchedNode(touchLocation: CGPoint) {
let touchedNode = self.atPoint(touchLocation)
if touchedNode is SKSpriteNode{
selectedNode = touchedNode as! SKSpriteNode
print ("touched")
} else {
selectedNode.removeAllActions()
}
}
func rotateNode(translation: CGPoint) {
selectedNode.zRotation = translation.y * 0.01
print ("Rotate")
print (translation.y)
}
However, my sprite nodes becomes "sticky" after I drag, i.e. if i've dragged sprite1, dragging the background of the scene still rotates sprite 1 until i drag sprite2, which then rotates sprite2 until I drag another node.
How can I "detach" the nodes so that it only rotates when I start the drag on their position?
How to detect if SCNBox was touched in scenekit? I am building a VR app
you can use this:
let tapGR = UITapGestureRecognizer(target: self, action: #selector(tapGesture(sender:)))
sceneView.addGestureRecognizer(tapGR)
#objc func tapGesture(sender: UITapGestureRecognizer) {
let location: CGPoint = (sender.location(in: self.sceneView))
let hits = self.sceneView.hitTest(location, options: nil)
if let tappedNode : SCNNode = hits.first?.node {
tappedNode.position.y += 0.5
print(tappedNode)
}
}
I am trying to move a UIView on slide up gesture from its initial position to a fixed final position. The image should move with the hand gesture, and not animate independently.
I haven't tried anything as I have no clue where to start, which gesture class to use.
Finally did it like below.
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
slideUpView.addGestureRecognizer(gesture)
slideUpView.userInteractionEnabled = true
gesture.delegate = self
The following function is called when the gesture is detected, (here I am restricting the view to have a maximum centre.y of 555, & I'm resetting back to 554 when the view moves past this point)
func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.Began || gestureRecognizer.state == UIGestureRecognizerState.Changed {
let translation = gestureRecognizer.translationInView(self.view)
print(gestureRecognizer.view!.center.y)
if(gestureRecognizer.view!.center.y < 555) {
gestureRecognizer.view!.center = CGPointMake(gestureRecognizer.view!.center.x, gestureRecognizer.view!.center.y + translation.y)
}else {
gestureRecognizer.view!.center = CGPointMake(gestureRecognizer.view!.center.x, 554)
}
gestureRecognizer.setTranslation(CGPointMake(0,0), inView: self.view)
}
}
You probably want to use a UIPanGestureRecognizer.
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
customView.addGestureRecognizer(gesture)
gesture.delegate = self
And to drag the object only along the y-axis:
func wasDragged(gesture: UIPanGestureRecognizer) {
let translation = gesture.translationInView(self.view)
// Use translation.y to change the position of your customView, e.g.
customView.center.y = translation.y // Customize this.
}
Swift 4:
#objc func wasDragged(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizer.State.began || gestureRecognizer.state == UIGestureRecognizer.State.changed {
let translation = gestureRecognizer.translation(in: self.view)
print(gestureRecognizer.view!.center.y)
if(gestureRecognizer.view!.center.y < 555) {
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x, y: gestureRecognizer.view!.center.y + translation.y)
}else {
gestureRecognizer.view!.center = CGPoint(x:gestureRecognizer.view!.center.x, y:554)
}
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
Call
let gesture = UIPanGestureRecognizer(target: self, action: self.wasDragged(gestureRecognizer:))
customView.addGestureRecognizer(gesture)
gesture.delegate = self
Update for Swift 3.x
When assigning the selector, the syntax has changed and now requires #selector
let gesture = UIPanGestureRecognizer(target: self, action: #selector(navViewDragged(gesture:)))
self.navLayoutView.addGestureRecognizer(gesture)
self.navLayoutView.isUserInteractionEnabled = true
gesture.delegate = self
Function implementation:
func navViewDragged(gesture: UIPanGestureRecognizer){
//Code here
}
Move view anywhere in Swift 3
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragged(gestureRecognizer:)))
demoView.isUserInteractionEnabled = true
demoView.addGestureRecognizer(panGesture)
Function
#objc func dragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {
let translation = gestureRecognizer.translation(in: self.view)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
This is how you really do it like the news view in the Stocks app
First add 2 constraints in Storyboard to the sliding view, one for it's state when it's fully opened and one for when it's closed. Don't forget to leave one of the constraints disabled / not installed so that your view will look opened or closed when the scene is reached.
Reference them in your code
#IBOutlet weak var optionsOpenedConstraint: NSLayoutConstraint!
#IBOutlet weak var optionsVisiableConstraint: NSLayoutConstraint!
now add the UIPanGestureRecognizer to your view in the viewDidLoad function.
let gesture = UIPanGestureRecognizer(target: self, action: #selector(type(of: self).wasDragged(gestureRecognizer:)))
optionsView.addGestureRecognizer(gesture)
finally add this callback and 2 functions:
#objc func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
let distanceFromBottom = screenHeight - gestureRecognizer.view!.center.y
if gestureRecognizer.state == UIGestureRecognizer.State.began || gestureRecognizer.state == UIGestureRecognizer.State.changed {
optionsOpenedConstraint.isActive = false
optionsVisiableConstraint.isActive = false
let translation = gestureRecognizer.translation(in: self.view)
if((distanceFromBottom - translation.y) < 100) {
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
if gestureRecognizer.state == UIGestureRecognizer.State.ended{
if distanceFromBottom > 6{
openOptionsPanel()
}else{
closeOptionsPanel()
}
}
}
func openOptionsPanel(){
optionsOpenedConstraint.isActive = true
optionsVisiableConstraint.isActive = false
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
func closeOptionsPanel(){
optionsOpenedConstraint.isActive = false
optionsVisiableConstraint.isActive = true
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
and voalá
#IBAction func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
if MainView.bounds.contains(mainImage.frame) {
let recognizerCenter = recognizer.location(in: MainView)
mainImage.center = recognizerCenter
}
if MainView.bounds.intersection(mainImage.frame).width > 50 && MainView.bounds.intersection(mainImage.frame).height > 0 {
let recognizerCenter = recognizer.location(in: MainView)
print(recognizerCenter)
mainImage.center = recognizerCenter
}
}
#IBAction func handlePinchGesture(_ recognizer: UIPinchGestureRecognizer) {
mainImage.transform = mainImage.transform.scaledBy(x: recognizer.scale, y: recognizer.scale)
recognizer.scale = 1.0
}
#IBAction func handleRotateGesture(_ recognizer: UIRotationGestureRecognizer) {
mainImage.transform = mainImage.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0.0
}
How to get the start point location of UIPanGesture and constantly calculate the moving distance? It would be better if you can provide some Swift code in your answer.
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: "catchPaned")
self.view.addGestureRecognizer(pan)
self.view.userInteractionEnabled = true
}
func catchPaned(gesture:UIPanGestureRecognizer){
switch(gesture.state){
case .Began:
let touchStart = gesture.locationInView(self.view)
case .Changed:
let distance = gesture.translationInView(self.view)
default:
println("default")
}
}
Get start point using locationInView,get distance using translationInView
I figure it out myself.
let panGesture = UIPanGestureRecognizer(target: self, action: Selector("panFunction:"))
myView.addGestureRecognizer(panGesture)
func panFunction(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(self.view)
let startPoint = CGPointZero
let distance = Double(translation.y - startPoint.y)
println("\(distance)")
}