Uitapgesturerecognizer - Make It Work on Touch Down, Not Touch Up

UITapGestureRecognizer - make it work on touch down, not touch up?

Create your custom TouchDownGestureRecognizer subclass and implement gesture in touchesBegan:

TouchDownGestureRecognizer.h

#import <UIKit/UIKit.h>

@interface TouchDownGestureRecognizer : UIGestureRecognizer

@end

TouchDownGestureRecognizer.m

#import "TouchDownGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>

@implementation TouchDownGestureRecognizer
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
if (self.state == UIGestureRecognizerStatePossible) {
self.state = UIGestureRecognizerStateRecognized;
}
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
self.state = UIGestureRecognizerStateFailed;
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
self.state = UIGestureRecognizerStateFailed;
}


@end

implementation:

#import "TouchDownGestureRecognizer.h"
TouchDownGestureRecognizer *touchDown = [[TouchDownGestureRecognizer alloc] initWithTarget:self action:@selector(handleTouchDown:)];
[yourView addGestureRecognizer:touchDown];

-(void)handleTouchDown:(TouchDownGestureRecognizer *)touchDown{
NSLog(@"Down");
}

Swift implementation:

import UIKit
import UIKit.UIGestureRecognizerSubclass

class TouchDownGestureRecognizer: UIGestureRecognizer
{
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent)
{
if self.state == .Possible
{
self.state = .Recognized
}
}

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent)
{
self.state = .Failed
}

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent)
{
self.state = .Failed
}
}

Here is the Swift syntax for 2017 to paste:

import UIKit.UIGestureRecognizerSubclass

class SingleTouchDownGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if self.state == .possible {
self.state = .recognized
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
}

Note that this is a drop-in replacement for UITap. So in code like...

func add(tap v:UIView, _ action:Selector) {
let t = UITapGestureRecognizer(target: self, action: action)
v.addGestureRecognizer(t)
}

you can safely swap to....

func add(hairtriggerTap v:UIView, _ action:Selector) {
let t = SingleTouchDownGestureRecognizer(target: self, action: action)
v.addGestureRecognizer(t)
}

Testing shows it will not be called more than once. It works as a drop-in replacement; you can just swap between the two calls.

iOS Detect tap down and touch up of a UIView

A Gesture Recognizer is probably overkill for what you want. You probably just want to use a combination of -touchesBegan:withEvent: and -touchesEnded:withEvent:.

This is flawed, but it should give you and idea of what you want to do.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.touchDown = YES;
self.backgroundColor = [UIColor redColor];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// Triggered when touch is released
if (self.isTouchDown) {
self.backgroundColor = [UIColor whiteColor];
self.touchDown = NO;
}
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
// Triggered if touch leaves view
if (self.isTouchDown) {
self.backgroundColor = [UIColor whiteColor];
self.touchDown = NO;
}
}

This code should go in a custom subclass of UIView that you create. Then use this custom view type instead of UIView and you'll get touch handling.

I add a UIGestureReconizer to a view to detect Touch Down action, but then the buttons above the view won't work anymore

You could compare that event in touchesBegan has touches for the view that is the same as the view to witch your gesture recognizer was assigned

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
guard let view = self.view else { return }
guard let touches = event.touches(for: view) else { return }
super.touchesBegan(touches, with: event)
if self.state == .possible {
self.state = .recognized
}
}

Touch up and Touch down action for UIImageView

Here is the solution:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//began
}
}
super.touchesBegan(touches, with: event)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//end
}
}
super.touchesEnded(touches, with: event)
}

Note: Put this inside a UIView SubClass and add: userInteractionEnabled = true inside the init block

How to Implement Touch Up Inside in touchesBegan, touchesEnded

Using the UILongPressGesturizer is actually a much better solution to mimic all of the functionality of a UIButton (touchUpInside, touchUpOutside, touchDown, etc.):

- (void) longPress:(UILongPressGestureRecognizer *)longPressGestureRecognizer
{
if (longPressGestureRecognizer.state == UIGestureRecognizerStateBegan || longPressGestureRecognizer.state == UIGestureRecognizerStateChanged)
{

CGPoint touchedPoint = [longPressGestureRecognizer locationInView: self];

if (CGRectContainsPoint(self.bounds, touchedPoint))
{
[self addHighlights];
}
else
{
[self removeHighlights];
}
}
else if (longPressGestureRecognizer.state == UIGestureRecognizerStateEnded)
{
if (self.highlightView.superview)
{
[self removeHighlights];
}

CGPoint touchedPoint = [longPressGestureRecognizer locationInView: self];

if (CGRectContainsPoint(self.bounds, touchedPoint))
{
if ([self.delegate respondsToSelector:@selector(buttonViewDidTouchUpInside:)])
{
[self.delegate buttonViewDidTouchUpInside:self];
}
}
}
}

Multiple UITapGestureRecognizer not working on UIScrollView

A tap gesture only has one state - "ended". You can't detect when a tap starts using a tap gesture. As you've seen, attempting to use two tap gestures doesn't accomplish what you want.

You need to implement the UIResponder methods touchesBegan and touchesEnded.

You may also want to see UITapGestureRecognizer - make it work on touch down, not touch up?
.

How do you detect a SwiftUI touchDown event with no movement or duration?

If you combine the code from these two questions:

How to detect a tap gesture location in SwiftUI?

UITapGestureRecognizer - make it work on touch down, not touch up?

You can make something like this:

ZStack {
Text("Test")
TapView {
print("Tapped")
}
}
struct TapView: UIViewRepresentable {
var tappedCallback: (() -> Void)

func makeUIView(context: UIViewRepresentableContext<TapView>) -> TapView.UIViewType {
let v = UIView(frame: .zero)
let gesture = SingleTouchDownGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.tapped))
v.addGestureRecognizer(gesture)
return v
}

class Coordinator: NSObject {
var tappedCallback: (() -> Void)

init(tappedCallback: @escaping (() -> Void)) {
self.tappedCallback = tappedCallback
}

@objc func tapped(gesture:UITapGestureRecognizer) {
self.tappedCallback()
}
}

func makeCoordinator() -> TapView.Coordinator {
return Coordinator(tappedCallback:self.tappedCallback)
}

func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<TapView>) {
}
}

class SingleTouchDownGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if self.state == .possible {
self.state = .recognized
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
}

There's definitely some abstractions we can make so that the usage is more like the other SwiftUI Gestures, but this is a start. Hopefully Apple builds in support for this at some point.

UITapGestureRecognizer Activate on Immediate Touch?

You need to implement the methods (touchesBegan: withEvent: etc) to detect the received touch in your UIViewController and check if the touch corresponds to the UIView of your interest. And from there you can trigger an action.

Check this thread how to implement the methods

Or this one

Touch down on UITapGestureRecogniser

No, that's not possible, but it shouldn't be hard to subclass UIGestureRecognizer to create your own recognizer that does that.

You could also use a UILongPressGestureRecognizer with the minimumPressDuration property set to 0.0. Note however that your action will then be called continuously when the touch moves, so make sure to check that the state of the recognizer is UIGestureRecognizerStateBegan in the action (this will only be once).

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

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)"



Related Topics



Leave a reply



Submit