Detect long touch in Sprite Kit - ios

I have a node in a SKScene that I am moving as per the users touch. Basically, this character should also be trying to follow the users finger (Assuming the finger is on the screen). I currently have it implemented as so, which works fine:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
player.runAction(SKAction.moveTo(touch.locationInNode(self), duration: 1))
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
player.runAction(SKAction.moveTo(touch.locationInNode(self), duration: 1))
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
player.removeAllActions()
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
player.removeAllActions()
}
However, the problem is that if a user holds his/her finger on the phone. The touchesBegan is only called once, and that's when the tap starts, not when it is held. I want the player character to constantly be trying to reach the finger.
I am centering the camera on the node, so the only time the node should be touching the finger is if the user puts his finger on/in the node (I.e the same position as the node). Because of this, after I run the SKAction to move the node the touch is invalid since it is at the old position.
How would I do this?

You can register a long touch event like this:
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
self.view.addGestureRecognizer(longPressRecognizer)
func longPressed(sender: UILongPressGestureRecognizer) {
// your code
}

For Swift 4:
You'll first want to make GameScene a UIGestureRecognizerDelegate:
class GameScene: SKScene, UIGestureRecognizerDelegate {
And so you'll also need to add the delegate method to the GameScene class:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Then inside GameScene's override func didMove(to view: SKView) { add the following:
let pressed:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPress(sender:)))
pressed.delegate = self
pressed.minimumPressDuration = 0.1
view.addGestureRecognizer(pressed)
Finally inside GameScene add your function to handle the long press (within which you can also discern the long press's state too):
func longPress(sender: UILongPressGestureRecognizer) {
if sender.state == .began { print("LongPress BEGAN detected") }
if sender.state == .ended { print("LongPress ENDED detected") }
}

Add two instance variables to your SKScene, BOOL fingerIsTouching and CGPoint touchLocation.
Inside -touchesBegan:, set fingerIsTouching to YES and update touchLocation to the correct location.
In your SKScene's -update: method, check if fingerIsTouching is YES and move your character according to touchLocation. I recommend using -update: because it is called once per frame, which is exactly enough. You might have to use some other method for moving the character than SKAction though.
Don't forget to update touchLocation in -touchesMoved:, and reset fingerIsTouching inside -touchesCancelled: and -touchesEnded: :)
Sorry for the Obj-C, hope this illustrates the point.

Related

Tap gesture to NOT occur when touching a specific location - SpriteKit

I have used touchesBegan to provide functionality for my UIButtons and have used a tapped gesture to provide functionality for my main player SKSpriteNode making it jump when triggered.
//Code regarding the UIButton touch
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//touches began is only used for GUI buttons -> not to affect player
for touch: AnyObject in touches {
//We get location of the touch
let locationOfTouch = touch.location(in: self)
if muteButton.contains(locationOfTouch) { //mute the game
timer.invalidate()
audioPlayer.volume = 0
}
//Code regarding the tap
let tap = UITapGestureRecognizer(target: self, action: #selector(GameScene.tapped(gesture:)))
tap.cancelsTouchesInView = false
self.view!.addGestureRecognizer(tap)
......
func tapped(gesture: UIGestureRecognizer) { //used to make the player jump
player.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 60))
player.physicsBody!.affectedByGravity = true */
}
My problem is that when I press on the restartButton the tap gesture is also activated later when the touch ends. Is there anything I can do?
The main issue is that the two separate systems for detecting touches (using gesture recognizers and using the touchesBegan/Moved/Ended methods) are in conflict.
One solution is to enable and disable the gesture recognizer if the touch is inside one of the buttons.
In the touchesBegan method, if the touch is inside a button, disable the tap gesture recognizer:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let locationOfTouch = touch.location(in: self)
if muteButton.contains(locationOfTouch) {
// mute action
tap.isEnabled = false
}
}
}
Then in touchesEnded and touchesCancelled, re-enable the gesture recognizer:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
tap.isEnabled = true
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
tap.isEnabled = true
}
This way, if the touch is inside a button, the tap gesture recognizer will not fire. Whenever any touch is complete, we always re-enable the gesture recognizer in case the next touch is meant to make the player jump.
I have tested this out in an empty project, and it works.
Hopefully that helps! Good luck with your game.

Function to detect if there's any touch occurring on the screen

I'm trying to create a simple function, similar to the touchesBegan, that detects if there's any touch occurring on the screen.
I've hit a brick wall trying it out myself because I'm not comfortable with UITouch class, but I really need some self made function, outside the touchesBegan default one.
I was trying to do something like this 'pseudo-code/swift'
func isTouchingTheScreen() -> Bool {
let someTouchHandleConstant: uitouch ???
if imTouchingTheScreen {
return true
} else {
return false
}
}
Do you have any hints?
PS: I know that code doesn't work, don't call that out, it was just to give you some 'image' of what I was trying to do (:
The idea
You can simply keep track of every touch begun, ended or cancelled by the user.
class GameScene: SKScene {
private var activeTouches = Set<UITouch>()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
activeTouches.unionInPlace(touches)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
activeTouches.subtractInPlace(touches)
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
if let touches = touches {
activeTouches.subtractInPlace(touches)
}
}
var isTouchingTheScreen: Bool { return !activeTouches.isEmpty }
}
Keeping activeTouches updated
As you can see I am keeping updated the activeTouches Set.
Every time a touch does begin I add it to activeTouches. And every time a touch does end or is cancelled I remove it from activeTouches.
The isTouchingTheScreen computed variable
This allows me to define the isTouchingTheScreen computed property that simply returns true when the Set contains some element.
You can implement UITapGestureRecognizer as below:
var tapGesture :UITapGestureRecognizer!
override func didMoveToNode() {
// Add UITapGestureRecognizer to view
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.touchedView(_:)))
view.addGestureRecognizer(tapGesture)
}
func touchedView(sender: UITapGestureRecognizer) {
print("view touched")
}
You could implement UITapGestureRecognizer:
// global var
var tapGesture :UITapGestureRecognizer!
override func didMoveToView() {
// Add tap gesture recognizer to view
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(GameScene.handleTap(_:)))
self.tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
func handleTap(sender: UITapGestureRecognizer) {
print("GameScene tap")
if sender.state == .Ended {
var positionInScene: CGPoint = sender.locationInView(sender.view)
positionInScene = self.scene!.convertPointFromView(positionInScene)
let touchedNode = self.nodeAtPoint(positionInScene)
if touchedNode.name != "myHero" {
print("The SKSpriteNode myHero was tapped")
}
}
}
You can find more details in Apple docs here.

3D Touch force gesture activates cell tap on touch end

I have subclassed a UITableView in my app so that I can intercept touch events. I am using this to allow me to provide 3D Touch gestures on the entire view (including on top of the table view).
This works great, however the problem is that using 3D Touch on one of the cells and then releasing your finger activates the cell tap.
I need to only activate the cell tap if there is no force exerted. I should explain that I am fading an image in gradually over the entire screen as you apply pressure.
Here is my subclass:
protocol PassTouchesTableViewDelegate {
func touchMoved(touches: Set<UITouch>)
func touchEnded(touches: Set<UITouch>)
}
class PassTouchesTableView: UITableView {
var delegatePass: PassTouchesTableViewDelegate?
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesMoved(touches, withEvent: event)
self.delegatePass?.touchMoved(touches)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
self.delegatePass?.touchEnded(touches)
}
}
And here are the methods I'm calling from my view controller when the touches end and move:
internal func touchMoved(touches: Set<UITouch>) {
let touch = touches.first
self.pressureImageView.alpha = (touch!.force / 2) - 1.0
}
internal func touchEnded(touches: Set<UITouch>) {
UIView.animateWithDuration(0.2, animations: {
self.pressureImageView.alpha = 0.0
})
}
You could create a boolean named isForceTouch which is set to false in touchesBegan, and then once force touch is detected, set it to true. Then in didSelectRowAtIndexPath just return false if isForceTouch is true. It may need tweaking but that should work.

how i can differentiate between a gesture and a touch in spriteKit and swift?

i have two functions, one is activated when i touch the scene and another one when i make a gesture down, but when i make a gesture the scene detect a touch and execute both functions
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// physics & collisions
physicsWorld.gravity = CGVectorMake(0.0, 0.0)
physicsWorld.contactDelegate = self
// Swipe down
let swipeDown:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedDown:"))
swipeDown.direction = .Down
view.addGestureRecognizer(swipeDown)
}
//MARK: Swipes
func swipedDown(sender:UISwipeGestureRecognizer){
//first function
swipeDown()
}
//MARK: Touches
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
// second function
attack()
}
}
}
Instead of touchesBegan, try using a tap gesture (UITapGestureRecognizer) for attack action.
This should not be necessary but if you still get conflict between swipe and tap, make the tap gesture depend on swipe's failure using requireGestureRecognizerToFail to ensure that a tap is not detected during a swipe.
You could try something like this
var swiped = Bool()
func swipedDown(sender:UISwipeGestureRecognizer){
//first function
swipeDown()
swiped = true
}
Override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
// second function
if swiped == false {
attack()
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
swiped = false
}
}
Hope this helps.

Scroll SpriteNode using touches

I want to scroll a node in sprite kit without uiscrollview
// In touchesMoved
let touch: UITouch = touches.first as! UITouch;
let location:CGPoint = touch.locationInNode(self);
let lastPosition = touch.previousLocationInNode(self)
var newLoc = selectNode.position.y + (location.y - lastPosition.y)
// Check if the top part is reached
if(newLoc < selectNodeStart){
newLoc = selectNodeStart
}
if(selectNode.position.y >= selectNodeStart){
// let sk = SKAction.moveToY(newLoc, duration: 0.1)
// selectNode.runAction(sk)
selectNode.position.y = newLoc
}
If I use the sk action the result is very horrible, but without the result is also horrible
An Elegant way of doing this in SpriteKit is using a UIPanGestureRecognizer to detect the touch, and inside of that you will create a sequence of SKActions that will accelerate or decelerate the movement. You might have to use the update method and/or touches to handle the pan stopping or another immediately starting. Unfortunately, if you want to use only SpriteKit you will have to use SKActions to implement this.
I thought I'd also mention an easier (and possible better looking) way to do this. Take a look at "ScrollKit", which works pretty well (although it does use a UIScrollView): https://github.com/bobmoff/ScrollKit
If you found that the UIScrollView doesn't pass touches up to touchesMoved, you could try a few different things.
- You can add a UITapGestureRecognizer and set on the UIScrollView CanCancelContentTouches to YES.
- You can subclass UIScrollView and override the touchesMethods so that if the user isn't scrolling, touch information will be passed up the responder chain:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent){
if (!self.dragging){
self.nextResponder.touchesBegan(touches , withEvent: event)
}
else{
super.touchesBegan(touches , withEvent: event)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent){
if (!self.dragging){
self.nextResponder.touchesMoved(touches , withEvent:event)
}
else{
super.touchesMoved(touches , withEvent: event)
}
}
override func touchesEnded(touches: NSSet, withEvent: event UIEvent){
if (!self.dragging){
self.nextResponder.touchesEnded(touches , withEvent: event)
}
else{
super.touchesEnded(touches , withEvent: event)
}
}

Resources