Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memoize tap GH state for sending proper value after releasing finger #478

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions ios/Handlers/RNTapHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

#import <React/RCTConvert.h>

// RNBetterTapGestureRecognizer extends UIGestureRecognizer instead of UITapGestureRecognizer
// because the latter does not allow for parameters like maxDelay, maxDuration, minPointers,
// RNBetterTapGestureRecognizer extends UIGestureRecognizer instead of UITapGestureRecognizer
// because the latter does not allow for parameters like maxDelay, maxDuration, minPointers,
// maxDelta to be configured. Using our custom implementation of tap recognizer we are able
// to support these.

Expand All @@ -26,6 +26,16 @@ @interface RNBetterTapGestureRecognizer : UIGestureRecognizer
@property (nonatomic) CGFloat maxDeltaX;
@property (nonatomic) CGFloat maxDeltaY;
@property (nonatomic) NSInteger minPointers;
// Memoizing positions and number of pointers are necessary
// since it might happen that changing state of recognizer could
// be delayed in order to wait for failure of another gesture recognition.
// Then data might be requested after raising finger which will
// lead to filling them with zeros if they are not memoized.
// This issue is only reasonable with continuous handlers and is
// handled properly with with implementation based on UIKit's recognizer
@property (nonatomic) CGPoint memoizedAbsolutePosition;
@property (nonatomic) CGPoint memoizedPosition;
@property (nonatomic) NSInteger memoizedNumberOfPointers;

- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler;

Expand Down Expand Up @@ -92,18 +102,18 @@ - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
if (numberOfTouches > _maxNumberOfTouches) {
_maxNumberOfTouches = numberOfTouches;
}

if (self.state != UIGestureRecognizerStatePossible) {
return;
}

if ([self shouldFailUnderCustomCriteria]) {
self.state = UIGestureRecognizerStateFailed;
[self triggerAction];
[self reset];
return;
}

self.state = UIGestureRecognizerStatePossible;
[self triggerAction];
}
Expand All @@ -120,7 +130,7 @@ - (BOOL)shouldFailUnderCustomCriteria
return YES;
}
}

CGPoint trans = [self translationInView];
if (TEST_MAX_IF_NOT_NAN(fabs(trans.x), _maxDeltaX)) {
return YES;
Expand All @@ -138,6 +148,9 @@ - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if (_numberOfTaps == _tapsSoFar && _maxNumberOfTouches >= _minPointers) {
_memoizedPosition = [self locationInView:self.view];
_memoizedAbsolutePosition = [self locationInView:self.view.window];
_memoizedNumberOfPointers = self.numberOfTouches;
self.state = UIGestureRecognizerStateEnded;
[self reset];
} else {
Expand Down Expand Up @@ -180,28 +193,44 @@ - (void)configure:(NSDictionary *)config
{
[super configure:config];
RNBetterTapGestureRecognizer *recognizer = (RNBetterTapGestureRecognizer *)_recognizer;

APPLY_INT_PROP(numberOfTaps);
APPLY_INT_PROP(minPointers);
APPLY_FLOAT_PROP(maxDeltaX);
APPLY_FLOAT_PROP(maxDeltaY);

id prop = config[@"maxDelayMs"];
if (prop != nil) {
recognizer.maxDelay = [RCTConvert CGFloat:prop] / 1000.0;
}

prop = config[@"maxDurationMs"];
if (prop != nil) {
recognizer.maxDuration = [RCTConvert CGFloat:prop] / 1000.0;
}

prop = config[@"maxDist"];
if (prop != nil) {
CGFloat dist = [RCTConvert CGFloat:prop];
recognizer.maxDistSq = dist * dist;
}
}

- (RNGestureHandlerEventExtraData *)eventExtraData:(RNBetterTapGestureRecognizer *)recognizer
{
if (recognizer.numberOfTouches == 0) {
// this condition is fulfilled if pointer is raised and it happens
// if is waiting for another one.
return [RNGestureHandlerEventExtraData
forPosition:recognizer.memoizedPosition
withAbsolutePosition:recognizer.memoizedAbsolutePosition
withNumberOfTouches:recognizer.memoizedNumberOfPointers];
}
return [RNGestureHandlerEventExtraData
forPosition:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches];
}

@end