Gesture recognizer with delaysTouchesBegan stops all calls to touchesMoved? - ios

The view controller code below demonstrates my question. It prints some debugging statements in response to touch events and a pinch gesture.
If the gesture recognizer is disabled, and I drag a finger on the screen, everything works as I expected: the controller gets a touchesBegan, a bunch of touchesMoved, and a touchesEnded. But if I enable the gesture recognizer and set its delaysTouchesBegan property to true, and drag a single finger, the controller receives only touchesBegan and touchesEnded calls - no touchesMoved.
The docs for delaysTouchesBegan suggest that touch events will pile up and be delivered after the gesture recognizer fails. Is this the expected behavior - to lose all the touchesMoved events?
class ViewController: UIViewController {
var gr: UIPinchGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
gr = UIPinchGestureRecognizer(target: self, action: #selector(pinched(_:)))
gr.delaysTouchesBegan = true
view.addGestureRecognizer(gr)
}
#IBAction func buttonTapped(_ sender: Any) {
gr.isEnabled = !gr.isEnabled
print("pinch recognizer enabled: \(gr.isEnabled)")
}
#objc func pinched(_ gr: Any?) {
print("pinched")
}
var i = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
i = 0
print("touchesBegan")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesMoved \(i)")
i += 1
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesEnded")
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesCancelled")
}
}

A pinch gesture recognizer reports changes each time the distance between the fingers change. The distance between the fingers is reported as a scale factor. So you will want to look at the scale factor, not touchesMoved. Was that helpful?
#objc func pinched(_ gr: UIPinchGestureRecognizer) {
if gr.state == .began {
print("began")
}
if gr.state == .changed {
print("scale: \(gr.scale)")
}
if gr.state == .ended {
print("ended")
}
}
Handling Pinch Gestures
Handling Pinch Gestures ( Swift 4 - 2018 )

Related

touchesBegan and touchesMoved also called during pinch

I have added a pinch gesture and a pan gesture in a view. I am also using touchesBegan and touchesMoved method. Surprisingly when i am pinching my view it is sometimes also calling touchesBegan and touchesMoved.How to stop calling this methods during pinching? Here is what i did.
class CustomView: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let touchCount = event?.touches(for: self)?.count {
if touchCount == 1 {
// Doing some task
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
if let touchCount = event?.touches(for: self)?.count {
if touchCount == 1 {
// Doing some task
}
}
}
}
In my ViewController: I created a customView object of CustomView class and added some gestures like below:
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(pinch(recognizer:)))
customView.addGestureRecognizer(pinch)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(recognizer:)))
panGesture.minimumNumberOfTouches = 2
self.customView.addGestureRecognizer(panGesture)
touchesBegan and touchesMoved are really, really generic -- whenever your finger touches down or moves, they'll be called. This includes button presses and just pretty much every thing that your finger can touch.
So if you're using gesture recognizers for pan and pinch, stick with gesture recognizers. Use an UITapGestureRecognizer instead of relying on touchesBegan, and another UIPanGestureRecognizer for touchesMoved.

UIPinchGestureRecognizer doesn't fire after custom UIGestureRecognizer fails

I have a custom UIGestureRecognizer subclass that detects a particular kind of one-finger dragging. On the same scene, I also have a UIPinchGestureRecognizer, that detects two-finger pinches.
The problem happens when the user makes a pinch gesture but puts down one finger an instant before the other: the custom gesture recognizer sees a single touch and engages (sets its state to .began) and the pinch recognizer doesn't. When the second touch is added, the custom recognizer notices and switches its state to .failed. But it's too late and the pinch gesture recognizer doesn't pick it up.
Here's the code for the custom UIGestureRecognizer subclass:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
guard touches.count == 1 else {
state = .failed
return
}
state = .began
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
guard touches.count == 1 else {
state = .failed
return
}
// do gesture recognizer stuff here
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
state = .ended
}
I've tried it with cancelsTouchesInView set to false and with delaysTouchesBegan set to true for the custom gesture recognizer. Neither made a difference in this behavior.
When I use a UIPinchGestureRecognizer with a UIPanGestureRecognizer I don't have this issue, which leads me to think it's not the intended behavior.
What's wrong with my UIGestureRecognizer fail code and how can I fix it so that a UIPinchGestureRecognizer can still recognize pinches where the touches start non-simultaneously?
Try using the UIGestureRecognizerDelegate protocol to implement gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:). That can allow you to have two gestures be recognized simultaneously.
class MyVC: UITableViewController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchFrom))
pinchGestureRecognizer.delegate = self
view.addGestureRecognizer(pinchGestureRecognizer)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}

Tracking Position in a custom UIGestureRecognizer

I'm building my own UIGestureRecognizer subclass in order to combine the tap and swipe gestures and track the direction of their swiping motion. In this intended gesture, the user would touch the screen, let their finger bounce fully off the screen one or more times before returning their finger to the screen and dragging; that is a tap followed immediately by a swipe.
I have been able to get the actual gesture portion of this recognizer to work properly, but where I am finding issue is in storing the trail of points where the user's swipe has traveled.
First, I initialize an array of CGPoints as a class member of my custom recognizer class
class UITapSwipeGestureRecognizer: UIGestureRecognizer {
...
var trail: [CGPoint] = []
Then, in touchesBegan(_, with), I first clear the array if not empty, and feed the starting point into the array
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
...
if !trail.isEmpty { // EXC_BAD_ACCESS error thrown here
trail.removeAll(keepingCapacity: true)
}
trail.append(location(in: view?.window))
...
}
I would go on to discuss my usage in touchesMoved(_, with), but this is enough to trigger my problem. Upon the first attempt to access the array, at !trail.isEmpty, I get the following error:
Thread 1 EXC_BAD_ACCESS (code=1, address=0x10)
Do I need to make a thread safe version of this array? If so, how should I go about doing this? Or am I just going about this entirely the wrong way?
I have been able to replicate your error, by adding the Gesture Recognizer through the StoryBoard.
When I add it programatically, I don't get the error.
Here's the Gesture Recognizer class
import UIKit
import UIKit.UIGestureRecognizerSubclass
class MyGestureClass: UIGestureRecognizer
{
var trail: [CGPoint] = []
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
state = .began
if !trail.isEmpty {
trail.removeAll(keepingCapacity: true)
}
trail.append(location(in: view?.window))
print(trail.count) // Just for debugging
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
trail.append(location(in: view?.window))
print(trail.count) // Just for debugging
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches , with: event)
state = .ended
print("Finished \(trail.count)") // Just for debugging
}
}
and here's how I access it from the View Controller
override func viewDidLoad()
{
super.viewDidLoad()
let myRecognizer = MyGestureClass(target: self, action: #selector(self.tap(_:)))
self.view.addGestureRecognizer(myRecognizer)
}
func tap(_ sender: UITapGestureRecognizer)
{
// No need to do anything here, but appears to be required
}

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.

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.

Resources