Fast tap gestures in Swift 4 and iOS 11 - ios

I just made a simple app to try out any type of gestures. I got to the tap gesture. So I thought, what if I made a fast tap game kind of application that counts the amount of taps the user performed. But soon enough I ran into some issues.
It did not count all the taps. If I began to tap as fast as possible, but it skipped taps.
The idea is I programmatically created a view in the superview and added a tapGestureRecognizer on the view. And simply put the “taps” into a label in the app.
It seems to fail to receive a system gesture on time.
The code:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(sender:)));
tap.numberOfTapsRequired = 1;
animationView.isUserInteractionEnabled = true;
animationView.addGestureRecognizer(tap);
The function:
#objc func tapped (sender :UITapGestureRecognizer) {
self.counter += 1;
self.lblScore.text = String(self.counter);
}
I have an animationView that I made "tappable" and it works. Every time I tap the animationView it increments the value of 'counter' that works! but every time I get this error if I tap too fast:
<_UISystemGestureGateGestureRecognizer: 0x1c01c4b00>: Gesture: Failed to receive system gesture state notification before next touch

Create Gesture :
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
panGesture.delegate = self
Button.addGestureRecognizer(panGesture)
let longTapGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongTapGesture(_:)))
Button.addGestureRecognizer(longTapGesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
Button.addGestureRecognizer(tapGesture)
let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotateGesture(_:)))
rotateGesture.delegate = self
Button.addGestureRecognizer(rotateGesture)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:)))
pinchGesture.delegate = self
Button.addGestureRecognizer(pinchGesture)
Gesture click Event :
extension AddTextVC: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#IBAction func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
}
#IBAction func handlePinchGesture(_ recognizer: UIPinchGestureRecognizer) {
}
#IBAction func handleRotateGesture(_ recognizer: UIRotationGestureRecognizer)
{
}
#IBAction func handleTapGesture(_ recognizer: UITapGestureRecognizer) {
}
#IBAction func handleLongTapGesture(_ recognizer: UITapGestureRecognizer) {
}
}

Related

figure out which button was pressed while differ between long-press and tap. swift

So I have listeners for a long and short-press on buttons, but I need to know what button was pressed.
Is it possible to find out what button was pressed in the tap and long function, or will I need to do two functions for each button?
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tap))
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(long))
longGesture.minimumPressDuration = 0.5
smallOption.addGestureRecognizer(tapGesture)
mediumOption.addGestureRecognizer(tapGesture)
largeOption.addGestureRecognizer(tapGesture)
smallOption.addGestureRecognizer(longGesture)
mediumOption.addGestureRecognizer(longGesture)
largeOption.addGestureRecognizer(longGesture)
#objc func tap(_ sender: UIGestureRecognizer){
print("short-press")
}
#objc func long(_ sender: UIGestureRecognizer){
print("long-press")
}
The main issue, in this case, is both gestures will be added ONLY for the largeOption button! To clarify, the gesture is added only for one component, which in your case, it should be added only for the latest one (which is largeOption):
smallOption.addGestureRecognizer(tapGesture) <-- skipped
mediumOption.addGestureRecognizer(tapGesture) <-- skipped
largeOption.addGestureRecognizer(tapGesture) <-- added
smallOption.addGestureRecognizer(longGesture) <-- skipped
mediumOption.addGestureRecognizer(longGesture) <-- skipped
largeOption.addGestureRecognizer(longGesture) <-- added
Logically speaking, this might be the answer to your question:
Is it possible to find out what button was pressed in the tap and long function, or will I need to do two functions for each button?
you need to add two gestures for each button because a particular gesture can only be added to one view.
However, you don't have to declare new action methods in addition to #objc func tap(_ sender: UIGestureRecognizer) and #objc func long(_ sender: UIGestureRecognizer) existing ones. What you could do instead is to check the sender's view. Example:
Let's assume that we manually added tow gestures for each button:
// gestures:
let smallOptionTapGesture = UITapGestureRecognizer(target: self, action: #selector(tap))
let smallOptionLongGesture = UILongPressGestureRecognizer(target: self, action: #selector(long))
smallOptionLongGesture.minimumPressDuration = 0.5
let mediumOptionTapGesture = UITapGestureRecognizer(target: self, action: #selector(tap))
let mediumOptionLongGesture = UILongPressGestureRecognizer(target: self, action: #selector(long))
mediumOptionLongGesture.minimumPressDuration = 0.5
let largeOptionTapGesture = UITapGestureRecognizer(target: self, action: #selector(tap))
let largeOptionLongGesture = UILongPressGestureRecognizer(target: self, action: #selector(long))
largeOptionLongGesture.minimumPressDuration = 0.5
// adding them:
smallOption.addGestureRecognizer(smallOptionTapGesture)
mediumOption.addGestureRecognizer(mediumOptionTapGesture)
largeOption.addGestureRecognizer(largeOptionTapGesture)
smallOption.addGestureRecognizer(smallOptionLongGesture)
mediumOption.addGestureRecognizer(mediumOptionLongGesture)
largeOption.addGestureRecognizer(largeOptionLongGesture)
Therefore, what you could do is:
#objc func tap(_ sender: UIGestureRecognizer) {
// an example of how you could check the button
if sender.view == smallOption {
print("small short-press")
} else if sender.view == mediumOption {
print("medium short-press")
} else if sender.view == largeOption {
print("large short-press")
}
}
#objc func long(_ sender: UIGestureRecognizer) {
// you could apply the same above approach here
}
The other option is to create action methods for each button separately.
You do not need two functions for the same gesture for each button. Change the method definition to accept UIButton (in your case) instead of UIGestureRecognizer. Afterwards, check the sender by verifying the tag or the type directly...
#objc func tap(_ sender: UIButton){
switch sender {
case button1: // or by tags of buttons
case button2:
...
default: break
}

Combining LongPress with Swipe UIGesture in Swift 5 (Xcode 10.1)

I've seen other people ask similar questions but I didn't really understand the answers. I'm new to iOS design and relatively new to Swift, so I apologize for my ignorance. My goal is to have the user swipe in a given direction and have the program repeat a certain action until the user lifts his finger from the screen. From what I can tell, I need to implement the gestureRecognizer function.
I think I probably implemented things in a weird way:
In my GameViewController.swift file, I implemented the following, as a separate function under the UIViewController class.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer)-> Bool {
return true
}
Which I understand to mean that any two UIGestureRecognizer actions can occur at the same time.
In my GameScene.swift file, under the SKScene class, I have:
override func didMove(to view: SKView) {
...
swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(swipeR))
swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(swipeL))
longPress = UILongPressGestureRecognizer(target: self, action: #selector(printLongPress))
longPress.delegate = self as? UIGestureRecognizerDelegate
swipeRight.delegate = self as? UIGestureRecognizerDelegate
swipeLeft.delegate = self as? UIGestureRecognizerDelegate
swipeRight.direction = .right
swipeLeft.direction = .left
view.addGestureRecognizer(swipeRight)
view.addGestureRecognizer(swipeLeft)
view.addGestureRecognizer(longPress)
}
#objc func swipeR() {
print("Right")
}
#objc func swipeL() {
print("Left")
}
#objc func printLongPress() {
print("Long press")
}
Ideally in the example code described here, if you were to swipe right and hold onto the screen, you'd see "Swipe Right" "Long Press" "Long Press" etc.
Any ideas what I'm doing wrong here?
Thanks in advance!

Resolve UITapGestureRecognizer and UILongPressGestureRecognizer simultaneously and fire UIGestureRecognizer.State.began on finger touch down

First of all, none of the already answered questions didn't help me
Swift: Long Press Gesture Recognizer - Detect taps and Long Press
Using tap gesture and long press at the same time in Table View
Long Press Gesture Recognizer Only Fired When Finger is Lifted
And so on
The code working almost fine except one thing: the long press gesture only called when I lift my finger up from the screen. But I need to get behaviour like in Instagram Stories (when you can switch between stories and hold your finger to pause pause some story).
My question is more about how to force UILongPressGesture to fire when user touch finger down, but not up.
Here's my code:
private func setupTapGestures() {
tapRecognizer = UITapGestureRecognizer()
tapRecognizer?.addTarget(self, action: #selector(handleTapGesture(_:)))
tapRecognizer?.delegate = self
view.addGestureRecognizer(tapRecognizer!)
longPressRecognizer = UILongPressGestureRecognizer()
longPressRecognizer?.addTarget(self, action: #selector(handleLongPressGesture(_:)))
longPressRecognizer?.minimumPressDuration = 0.1
longPressRecognizer?.delegate = self
view.addGestureRecognizer(longPressRecognizer!)
}
#objc func handleTapGesture(_ gestureRecognizer: UIGestureRecognizer) {
let width = view.frame.width
let point = gestureRecognizer.location(in: view)
viewModel?.tapAction(viewWidth: width, tapPoint: point)
Swift.print("Tap gesture")
}
#objc func handleLongPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
if gestureRecognizer.state == .began {
Swift.print("Began")
} else if gestureRecognizer.state == .ended {
Swift.print("Ended")
}
}
UIGestureRecognizerDelegate:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Don't recognize a single tap until a long-press fails
if gestureRecognizer == tapRecognizer && otherGestureRecognizer == longPressRecognizer {
return true
}
return false
}
shouldRequireFailureOf docs
Any suggestions or ideas?
I wonder if your implementation of shouldRequireFailureOf is causing an issue?
This works just fine for me (note: I used .minimumPressDuration = 0.25 because it's a little difficult to tap in under 0.1 seconds):
class GestureTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupTapGestures()
}
private func setupTapGestures() -> Void {
let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
view.addGestureRecognizer(singleTapGesture)
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
longPressGesture.minimumPressDuration = 0.25
view.addGestureRecognizer(longPressGesture)
}
#objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) -> Void {
if gesture.state == .began {
print("Did Long Press (began)")
}
if gesture.state == .ended {
print("Did Long Press (ended)")
}
}
#objc func handleTapGesture(_ gesture: UITapGestureRecognizer) -> Void {
print("Did Single Tap")
}
}
When I tap, I get "Did Single Tap" in the debug console.
When I tap and hold, I quickly get "Did Long Press (began)", and on finger-lift I get "Did Long Press (ended)"

How to detect finger location in function

I want to use double click. I have written function doubleTap. How to recognize location of finger?
override func viewDidLoad()
{
super.viewDidLoad()
let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTap")
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTap()
{
}
You can use location(ofTouch:in:) to get the location of the touch. However, you need access to the gesture recognizer from inside the function where you want to access the location, so you should declare doubleTap as an instance property of your class.
class YourViewController: UIViewController {
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(YourViewController.doubleTap))
override func viewDidLoad(){
super.viewDidLoad()
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTap(){
let touchLocation = doubleTap.location(ofTouch: numberOfTouches-1,in: nil)
}
}
Change the input parameters to the function if you want to change whether you need to get the first or last touch's location or if you want to get the location relative to a subview.
You can get the gesture recognizer as a parameter
override func viewDidLoad()
{
super.viewDidLoad()
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapFunc))
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTapFunc(_ sender: UITapGestureRecognizer)
{
// Use 'sender' here to get the location
if sender.state == .ended {
// handling code
let location = sender.location(ofTouch: 0, in: self.view!)
}
}

Customize long press gesture recognizer

I want to customize my long press gesture recognizer in the following ways:
1) When I hold down on an object for 0.5 seconds, the object darkens, and
2) When I continue to hold down on the object for another second (total of 1.5 seconds), some action happens (e.g. the object disappears).
Essentially, by holding down on an object for a minimum of 1.5 seconds, two actions happened at two separate times. I also have a tap gesture recognizer, which might affect things.
The answer from #nathan is essentially fine but a detail is missing you need to implement the UIGestureRecognizerDelegate to allow both gestures works simultaneously, so this is my code
class ViewController: UIViewController, UIGestureRecognizerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//this for .5 time
let firstGesture = UILongPressGestureRecognizer(target: self, action: #selector(firstMethod))
//this for 1.5
let secondGesture = UILongPressGestureRecognizer(target: self, action: #selector(secondMethod))
secondGesture.minimumPressDuration = 1.5
firstGesture.delegate = self
secondGesture.delegate = self
self.view.addGestureRecognizer(firstGesture)
self.view.addGestureRecognizer(secondGesture)
}
func firstMethod() {
debugPrint("short")
}
func secondMethod() {
debugPrint("long")
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool{
return true
}
}
Hope this help
See Reinier's solution as it's the correct one. This one adds a delay to satisfy require(toFail:)
You can set the timing using the property minimumPressDuration (in seconds, default is 0.5)
let quickActionPress = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.zeroFiveSecondPress(gesture:))) // 0.5 seconds by default
let laterActionPress = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.oneFiveSecondPress(gesture:)))
laterActionPress.minimumPressDuration = 1.5
someView.addGestureRecognizer(quickActionPress)
someView.addGestureRecognizer(laterActionPress)
// If 1.5 detected, only execute that one
quickActionPress.require(toFail: laterActionPress)
#objc func zeroFiveSecondPress(gesture: UIGestureRecognizer) {
// Do something
print("0.5 press")
}
#objc func oneFiveSecondPress(gesture: UIGestureRecognizer) {
zeroFiveSecondPress(gesture: gesture)
// Do something else
print("1.5 press")
}

Resources