feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,4 @@
#import "RNGestureHandler.h"
@interface RNFlingGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,173 @@
#import "RNFlingHandler.h"
#if !TARGET_OS_OSX
@interface RNBetterSwipeGestureRecognizer : UISwipeGestureRecognizer
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@end
@implementation RNBetterSwipeGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
CGPoint _lastPoint; // location of the most recently updated touch, relative to the view
bool _hasBegan; // whether the `BEGAN` event has been sent
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_lastPoint = CGPointZero;
_hasBegan = NO;
}
return self;
}
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
_lastPoint = [[[touches allObjects] objectAtIndex:0] locationInView:_gestureHandler.recognizer.view];
[_gestureHandler reset];
[super touchesBegan:touches withEvent:event];
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
// self.numberOfTouches doesn't work for this because in case than one finger is required,
// when holding one finger on the screen and tapping with the second one, numberOfTouches is equal
// to 2 only for the first tap but 1 for all the following ones
if (!_hasBegan) {
[self triggerAction];
_hasBegan = YES;
}
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
_lastPoint = [[[touches allObjects] objectAtIndex:0] locationInView:_gestureHandler.recognizer.view];
[super touchesMoved:touches withEvent:event];
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
_lastPoint = [[[touches allObjects] objectAtIndex:0] locationInView:_gestureHandler.recognizer.view];
[super touchesEnded:touches withEvent:event];
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
_lastPoint = [[[touches allObjects] objectAtIndex:0] locationInView:_gestureHandler.recognizer.view];
[super touchesCancelled:touches withEvent:event];
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
}
- (void)triggerAction
{
[_gestureHandler handleGesture:self];
}
- (void)reset
{
[self triggerAction];
[_gestureHandler.pointerTracker reset];
_hasBegan = NO;
[super reset];
[_gestureHandler reset];
}
- (CGPoint)getLastLocation
{
// I think keeping the location of only one touch is enough since it would be used to determine the direction
// of the movement, and if it's wrong the recognizer fails anyway.
// In case the location of all touches is required, touch events are the way to go
return _lastPoint;
}
@end
@implementation RNFlingGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNBetterSwipeGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)resetConfig
{
[super resetConfig];
UISwipeGestureRecognizer *recognizer = (UISwipeGestureRecognizer *)_recognizer;
recognizer.direction = UISwipeGestureRecognizerDirectionRight;
#if !TARGET_OS_TV
recognizer.numberOfTouchesRequired = 1;
#endif
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
UISwipeGestureRecognizer *recognizer = (UISwipeGestureRecognizer *)_recognizer;
id prop = config[@"direction"];
if (prop != nil) {
recognizer.direction = [RCTConvert NSInteger:prop];
}
#if !TARGET_OS_TV
prop = config[@"numberOfPointers"];
if (prop != nil) {
recognizer.numberOfTouchesRequired = [RCTConvert NSInteger:prop];
}
#endif
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
RNGestureHandlerState savedState = _lastState;
BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer];
_lastState = savedState;
return shouldBegin;
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(id)_recognizer
{
// For some weird reason [recognizer locationInView:recognizer.view.window] returns (0, 0).
// To calculate the correct absolute position, first calculate the absolute position of the
// view inside the root view controller (https://stackoverflow.com/a/7448573) and then
// add the relative touch position to it.
RNBetterSwipeGestureRecognizer *recognizer = (RNBetterSwipeGestureRecognizer *)_recognizer;
CGPoint viewAbsolutePosition = [recognizer.view convertPoint:recognizer.view.bounds.origin
toView:RCTKeyWindow().rootViewController.view];
CGPoint locationInView = [recognizer getLastLocation];
return [RNGestureHandlerEventExtraData
forPosition:locationInView
withAbsolutePosition:CGPointMake(
viewAbsolutePosition.x + locationInView.x, viewAbsolutePosition.y + locationInView.y)
withNumberOfTouches:recognizer.numberOfTouches
withPointerType:_pointerType];
}
@end
#else
@implementation RNFlingGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
RCTLogWarn(@"FlingGestureHandler is not supported on macOS");
if ((self = [super initWithTag:tag])) {
_recognizer = [NSGestureRecognizer alloc];
}
return self;
}
@end
#endif

View File

@@ -0,0 +1,4 @@
#import "RNGestureHandler.h"
@interface RNForceTouchHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,195 @@
#import "RNForceTouchHandler.h"
#if !TARGET_OS_OSX
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/RCTConvert.h>
@interface RNForceTouchGestureRecognizer : UIGestureRecognizer
@property (nonatomic) CGFloat maxForce;
@property (nonatomic) CGFloat minForce;
@property (nonatomic) CGFloat force;
@property (nonatomic) BOOL feedbackOnActivation;
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@end
@implementation RNForceTouchGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
RNGHUITouch *_firstTouch;
}
static const CGFloat defaultForce = 0;
static const CGFloat defaultMinForce = 0.2;
static const CGFloat defaultMaxForce = NAN;
static const BOOL defaultFeedbackOnActivation = NO;
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_force = defaultForce;
_minForce = defaultMinForce;
_maxForce = defaultMaxForce;
_feedbackOnActivation = defaultFeedbackOnActivation;
}
return self;
}
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
if (_firstTouch) {
// ignore rest of fingers
return;
}
[super touchesBegan:touches withEvent:event];
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
_firstTouch = [touches anyObject];
[self handleForceWithTouches:touches];
self.state = UIGestureRecognizerStatePossible;
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
if (![touches containsObject:_firstTouch]) {
// Considered only the very first touch
return;
}
[super touchesMoved:touches withEvent:event];
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
[self handleForceWithTouches:touches];
if ([self shouldFail]) {
self.state = UIGestureRecognizerStateFailed;
return;
}
if (self.state == UIGestureRecognizerStatePossible && [self shouldActivate]) {
[self performFeedbackIfRequired];
self.state = UIGestureRecognizerStateBegan;
}
}
- (BOOL)shouldActivate
{
return (_force >= _minForce);
}
- (BOOL)shouldFail
{
return TEST_MAX_IF_NOT_NAN(_force, _maxForce);
}
- (void)performFeedbackIfRequired
{
#if !TARGET_OS_TV && !TARGET_OS_VISION
if (_feedbackOnActivation) {
if (@available(iOS 10.0, *)) {
[[[UIImpactFeedbackGenerator alloc] initWithStyle:(UIImpactFeedbackStyleMedium)] impactOccurred];
}
}
#endif
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
if (![touches containsObject:_firstTouch]) {
// Considered only the very first touch
return;
}
[super touchesEnded:touches withEvent:event];
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
if (self.state == UIGestureRecognizerStateBegan || self.state == UIGestureRecognizerStateChanged) {
self.state = UIGestureRecognizerStateEnded;
} else {
self.state = UIGestureRecognizerStateFailed;
}
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
}
- (void)handleForceWithTouches:(NSSet<RNGHUITouch *> *)touches
{
_force = _firstTouch.force / _firstTouch.maximumPossibleForce;
}
- (void)reset
{
[_gestureHandler.pointerTracker reset];
[super reset];
[_gestureHandler reset];
_force = 0;
_firstTouch = NULL;
}
@end
@implementation RNForceTouchHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNForceTouchGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)resetConfig
{
[super resetConfig];
RNForceTouchGestureRecognizer *recognizer = (RNForceTouchGestureRecognizer *)_recognizer;
recognizer.feedbackOnActivation = defaultFeedbackOnActivation;
recognizer.maxForce = defaultMaxForce;
recognizer.minForce = defaultMinForce;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
RNForceTouchGestureRecognizer *recognizer = (RNForceTouchGestureRecognizer *)_recognizer;
APPLY_FLOAT_PROP(maxForce);
APPLY_FLOAT_PROP(minForce);
id prop = config[@"feedbackOnActivation"];
if (prop != nil) {
recognizer.feedbackOnActivation = [RCTConvert BOOL:prop];
}
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(RNForceTouchGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forForce:recognizer.force
forPosition:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches
withPointerType:_pointerType];
}
@end
#else
@implementation RNForceTouchHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [NSGestureRecognizer alloc];
}
return self;
}
@end
#endif

View File

@@ -0,0 +1,12 @@
//
// RNHoverHandler.h
// RNGestureHandler
//
// Created by Jakub Piasecki on 31/03/2023.
//
#import "RNGestureHandler.h"
API_AVAILABLE(ios(13.4))
@interface RNHoverGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,179 @@
//
// RNHoverHandler.m
// RNGestureHandler
//
// Created by Jakub Piasecki on 31/03/2023.
//
#import "RNHoverHandler.h"
#if !TARGET_OS_OSX
#import <React/RCTConvert.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#define CHECK_TARGET(__VERSION__) \
defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_##__VERSION__) && \
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_##__VERSION__ && !TARGET_OS_TV
typedef NS_ENUM(NSInteger, RNGestureHandlerHoverEffect) {
RNGestureHandlerHoverEffectNone = 0,
RNGestureHandlerHoverEffectLift,
RNGestureHandlerHoverEffectHightlight,
};
#if CHECK_TARGET(13_4)
API_AVAILABLE(ios(13.4))
@interface RNBetterHoverGestureRecognizer : UIHoverGestureRecognizer <UIPointerInteractionDelegate>
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@property (nonatomic) RNGestureHandlerHoverEffect hoverEffect;
@end
@implementation RNBetterHoverGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_hoverEffect = RNGestureHandlerHoverEffectNone;
}
return self;
}
- (void)triggerAction
{
[_gestureHandler handleGesture:self];
}
- (void)cancel
{
self.enabled = NO;
}
- (void)reset
{
[super reset];
[_gestureHandler reset];
}
- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region
{
if (interaction.view != nil && _hoverEffect != RNGestureHandlerHoverEffectNone) {
UITargetedPreview *preview = [[UITargetedPreview alloc] initWithView:interaction.view];
UIPointerEffect *effect = nil;
if (_hoverEffect == RNGestureHandlerHoverEffectLift) {
effect = [UIPointerLiftEffect effectWithPreview:preview];
} else if (_hoverEffect == RNGestureHandlerHoverEffectHightlight) {
effect = [UIPointerHoverEffect effectWithPreview:preview];
}
return [UIPointerStyle styleWithEffect:effect shape:nil];
}
return nil;
}
@end
#endif
@implementation RNHoverGestureHandler {
#if CHECK_TARGET(13_4)
UIPointerInteraction *_pointerInteraction;
#endif
}
- (instancetype)initWithTag:(NSNumber *)tag
{
#if TARGET_OS_TV
RCTLogWarn(@"HoverGestureHandler is not supported on tvOS");
#endif
if ((self = [super initWithTag:tag])) {
#if CHECK_TARGET(13_4)
if (@available(iOS 13.4, *)) {
_recognizer = [[RNBetterHoverGestureRecognizer alloc] initWithGestureHandler:self];
_pointerInteraction =
[[UIPointerInteraction alloc] initWithDelegate:(id<UIPointerInteractionDelegate>)_recognizer];
}
#endif
}
return self;
}
- (void)bindToView:(UIView *)view
{
#if CHECK_TARGET(13_4)
if (@available(iOS 13.4, *)) {
[super bindToView:view];
[view addInteraction:_pointerInteraction];
}
#endif
}
- (void)unbindFromView
{
#if CHECK_TARGET(13_4)
if (@available(iOS 13.4, *)) {
[super unbindFromView];
[self.recognizer.view removeInteraction:_pointerInteraction];
}
#endif
}
- (void)resetConfig
{
[super resetConfig];
#if CHECK_TARGET(13_4)
if (@available(iOS 13.4, *)) {
RNBetterHoverGestureRecognizer *recognizer = (RNBetterHoverGestureRecognizer *)_recognizer;
recognizer.hoverEffect = RNGestureHandlerHoverEffectNone;
}
#endif
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
#if CHECK_TARGET(13_4)
if (@available(iOS 13.4, *)) {
RNBetterHoverGestureRecognizer *recognizer = (RNBetterHoverGestureRecognizer *)_recognizer;
APPLY_INT_PROP(hoverEffect);
}
#endif
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withPointerType:UITouchTypePencil];
}
@end
#else
@implementation RNHoverGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
RCTLogWarn(@"HoverGestureHandler is not supported on macOS");
if ((self = [super initWithTag:tag])) {
_recognizer = [NSGestureRecognizer alloc];
}
return self;
}
@end
#endif

View File

@@ -0,0 +1,12 @@
//
// RNLongPressHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNLongPressGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,197 @@
//
// RNLongPressHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNLongPressHandler.h"
#if !TARGET_OS_OSX
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/RCTConvert.h>
@interface RNBetterLongPressGestureRecognizer : UILongPressGestureRecognizer {
CFTimeInterval startTime;
CFTimeInterval previousTime;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
- (void)handleGesture:(UIGestureRecognizer *)recognizer;
- (NSUInteger)getDuration;
@end
@implementation RNBetterLongPressGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
CGPoint _initPosition;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:self action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
}
return self;
}
- (void)handleGesture:(UIGestureRecognizer *)recognizer
{
previousTime = CACurrentMediaTime();
[_gestureHandler handleGesture:recognizer];
}
- (void)triggerAction
{
[self handleGesture:self];
}
- (CGPoint)translationInView
{
CGPoint currentPosition = [self locationInView:self.view];
return CGPointMake(currentPosition.x - _initPosition.x, currentPosition.y - _initPosition.y);
}
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
[super touchesBegan:touches withEvent:event];
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
_initPosition = [self locationInView:self.view];
startTime = CACurrentMediaTime();
[_gestureHandler reset];
[self triggerAction];
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
CGPoint trans = [self translationInView];
if ((_gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]) ||
(TEST_MAX_IF_NOT_NAN(
fabs(trans.y * trans.y + trans.x + trans.x), self.allowableMovement * self.allowableMovement))) {
self.enabled = NO;
self.enabled = YES;
}
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
}
- (void)reset
{
if (self.state == UIGestureRecognizerStateFailed) {
[self triggerAction];
}
[_gestureHandler.pointerTracker reset];
[super reset];
[_gestureHandler reset];
}
- (NSUInteger)getDuration
{
return (previousTime - startTime) * 1000;
}
@end
@implementation RNLongPressGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNBetterLongPressGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)resetConfig
{
[super resetConfig];
UILongPressGestureRecognizer *recognizer = (UILongPressGestureRecognizer *)_recognizer;
recognizer.minimumPressDuration = 0.5;
recognizer.allowableMovement = 10;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
UILongPressGestureRecognizer *recognizer = (UILongPressGestureRecognizer *)_recognizer;
id prop = config[@"minDurationMs"];
if (prop != nil) {
recognizer.minimumPressDuration = [RCTConvert CGFloat:prop] / 1000.0;
}
prop = config[@"maxDist"];
if (prop != nil) {
recognizer.allowableMovement = [RCTConvert CGFloat:prop];
}
}
- (RNGestureHandlerState)state
{
// For long press recognizer we treat "Began" state as "active"
// as it changes its state to "Began" as soon as the the minimum
// hold duration timeout is reached, whereas state "Changed" is
// only set after "Began" phase if there is some movement.
if (_recognizer.state == UIGestureRecognizerStateBegan) {
return RNGestureHandlerStateActive;
}
return [super state];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// same as TapGH, this needs to be unified when all handlers are updated
RNGestureHandlerState savedState = _lastState;
BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer];
_lastState = savedState;
return shouldBegin;
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches
withDuration:[(RNBetterLongPressGestureRecognizer *)recognizer getDuration]
withPointerType:_pointerType];
}
@end
#else
@implementation RNLongPressGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
RCTLogWarn(@"LongPressGestureHandler is not supported on macOS");
if ((self = [super initWithTag:tag])) {
_recognizer = [NSGestureRecognizer alloc];
}
return self;
}
@end
#endif

View File

@@ -0,0 +1,4 @@
#import "RNGestureHandler.h"
@interface RNManualGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,109 @@
#import "RNManualHandler.h"
#if !TARGET_OS_OSX
@interface RNManualRecognizer : UIGestureRecognizer
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@end
@implementation RNManualRecognizer {
__weak RNGestureHandler *_gestureHandler;
BOOL _shouldSendBeginEvent;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_shouldSendBeginEvent = YES;
}
return self;
}
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
[super touchesBegan:touches withEvent:event];
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
if (_shouldSendBeginEvent) {
[_gestureHandler handleGesture:self];
_shouldSendBeginEvent = NO;
}
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
if ([self shouldFail]) {
self.state = (self.state == UIGestureRecognizerStatePossible) ? UIGestureRecognizerStateFailed
: UIGestureRecognizerStateCancelled;
[self reset];
}
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
}
- (void)reset
{
[_gestureHandler.pointerTracker reset];
[super reset];
[_gestureHandler reset];
_shouldSendBeginEvent = YES;
}
- (BOOL)shouldFail
{
if (_gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]) {
return YES;
} else {
return NO;
}
}
@end
@implementation RNManualGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNManualRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
@end
#else
@implementation RNManualGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
RCTLogWarn(@"ManualGestureHandler is not supported on macOS");
if ((self = [super initWithTag:tag])) {
_recognizer = [NSGestureRecognizer alloc];
}
return self;
}
@end
#endif

View File

@@ -0,0 +1,15 @@
//
// RNNativeViewHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNDummyGestureRecognizer : UIGestureRecognizer
@end
@interface RNNativeViewGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,231 @@
//
// RNNativeViewHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNNativeViewHandler.h"
#if !TARGET_OS_OSX
#import <UIKit/UIGestureRecognizerSubclass.h>
#endif
#import <React/RCTConvert.h>
#import <React/UIView+React.h>
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTScrollViewComponentView.h>
#else
#import <React/RCTScrollView.h>
#endif // RCT_NEW_ARCH_ENABLED
#if !TARGET_OS_OSX
#pragma mark RNDummyGestureRecognizer
@implementation RNDummyGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
}
return self;
}
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
self.state = UIGestureRecognizerStateFailed;
[self reset];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateCancelled;
[self reset];
}
- (void)reset
{
[_gestureHandler.pointerTracker reset];
[super reset];
[_gestureHandler reset];
}
@end
#pragma mark RNNativeViewGestureHandler
@implementation RNNativeViewGestureHandler {
BOOL _shouldActivateOnStart;
BOOL _disallowInterruption;
}
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNDummyGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
_shouldActivateOnStart = [RCTConvert BOOL:config[@"shouldActivateOnStart"]];
_disallowInterruption = [RCTConvert BOOL:config[@"disallowInterruption"]];
}
- (void)bindToView:(UIView *)view
{
// For UIControl based views (UIButton, UISwitch) we provide special handling that would allow
// for properties like `disallowInterruption` to work.
if ([view isKindOfClass:[UIControl class]]) {
UIControl *control = (UIControl *)view;
[control addTarget:self action:@selector(handleTouchDown:forEvent:) forControlEvents:UIControlEventTouchDown];
[control addTarget:self
action:@selector(handleTouchUpOutside:forEvent:)
forControlEvents:UIControlEventTouchUpOutside];
[control addTarget:self
action:@selector(handleTouchUpInside:forEvent:)
forControlEvents:UIControlEventTouchUpInside];
[control addTarget:self action:@selector(handleDragExit:forEvent:) forControlEvents:UIControlEventTouchDragExit];
[control addTarget:self action:@selector(handleDragEnter:forEvent:) forControlEvents:UIControlEventTouchDragEnter];
[control addTarget:self action:@selector(handleTouchCancel:forEvent:) forControlEvents:UIControlEventTouchCancel];
} else {
[super bindToView:view];
}
// We can restore default scrollview behaviour to delay touches to scrollview's children
// because gesture handler system can handle cancellation of scroll recognizer when JS responder
// is set
#ifdef RCT_NEW_ARCH_ENABLED
if ([view isKindOfClass:[RCTScrollViewComponentView class]]) {
UIScrollView *scrollView = ((RCTScrollViewComponentView *)view).scrollView;
scrollView.delaysContentTouches = YES;
}
#else
if ([view isKindOfClass:[RCTScrollView class]]) {
// This part of the code is coupled with RN implementation of ScrollView native wrapper and
// we expect for RCTScrollView component to contain a subclass of UIScrollview as the only
// subview
UIScrollView *scrollView = [view.subviews objectAtIndex:0];
scrollView.delaysContentTouches = YES;
}
#endif // RCT_NEW_ARCH_ENABLED
}
- (void)handleTouchDown:(UIView *)sender forEvent:(UIEvent *)event
{
[self setCurrentPointerType:event];
[self reset];
if (_disallowInterruption) {
// When `disallowInterruption` is set we cancel all gesture handlers when this UIControl
// gets DOWN event
for (RNGHUITouch *touch in [event allTouches]) {
for (UIGestureRecognizer *recogn in [touch gestureRecognizers]) {
recogn.enabled = NO;
recogn.enabled = YES;
}
}
}
[self sendEventsInState:RNGestureHandlerStateActive
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES withPointerType:_pointerType]];
}
- (void)handleTouchUpOutside:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateEnd
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO withPointerType:_pointerType]];
}
- (void)handleTouchUpInside:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateEnd
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES withPointerType:_pointerType]];
}
- (void)handleDragExit:(UIView *)sender forEvent:(UIEvent *)event
{
// Pointer is moved outside of the view bounds, we cancel button when `shouldCancelWhenOutside` is set
if (self.shouldCancelWhenOutside) {
UIControl *control = (UIControl *)sender;
[control cancelTrackingWithEvent:event];
[self sendEventsInState:RNGestureHandlerStateEnd
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO withPointerType:_pointerType]];
} else {
[self sendEventsInState:RNGestureHandlerStateActive
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO withPointerType:_pointerType]];
}
}
- (void)handleDragEnter:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateActive
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES withPointerType:_pointerType]];
}
- (void)handleTouchCancel:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateCancelled
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO withPointerType:_pointerType]];
}
@end
#else
#pragma mark RNDummyGestureRecognizer
@implementation RNDummyGestureRecognizer
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)];
return self;
}
@end
#pragma mark RNNativeViewGestureHandler
@implementation RNNativeViewGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
RCTLogWarn(@"NativeViewGestureHandler is not supported on macOS");
if ((self = [super initWithTag:tag])) {
_recognizer = [NSGestureRecognizer alloc];
}
return self;
}
@end
#endif

View File

@@ -0,0 +1,12 @@
//
// RNPanHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNPanGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,421 @@
//
// RNPanHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNPanHandler.h"
#if TARGET_OS_OSX
@interface RNBetterPanGestureRecognizer : NSPanGestureRecognizer
#else
#import <UIKit/UIGestureRecognizerSubclass.h>
@interface RNBetterPanGestureRecognizer : UIPanGestureRecognizer
#endif
@property (nonatomic) CGFloat minDistSq;
@property (nonatomic) CGFloat minVelocityX;
@property (nonatomic) CGFloat minVelocityY;
@property (nonatomic) CGFloat minVelocitySq;
@property (nonatomic) CGFloat activeOffsetXStart;
@property (nonatomic) CGFloat activeOffsetXEnd;
@property (nonatomic) CGFloat failOffsetXStart;
@property (nonatomic) CGFloat failOffsetXEnd;
@property (nonatomic) CGFloat activeOffsetYStart;
@property (nonatomic) CGFloat activeOffsetYEnd;
@property (nonatomic) CGFloat failOffsetYStart;
@property (nonatomic) CGFloat failOffsetYEnd;
@property (nonatomic) CGFloat activateAfterLongPress;
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@end
@implementation RNBetterPanGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
#if !TARGET_OS_OSX
NSUInteger _realMinimumNumberOfTouches;
#endif
BOOL _hasCustomActivationCriteria;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_minDistSq = NAN;
_minVelocityX = NAN;
_minVelocityY = NAN;
_minVelocitySq = NAN;
_activeOffsetXStart = NAN;
_activeOffsetXEnd = NAN;
_failOffsetXStart = NAN;
_failOffsetXEnd = NAN;
_activeOffsetYStart = NAN;
_activeOffsetYEnd = NAN;
_failOffsetYStart = NAN;
_failOffsetYEnd = NAN;
_activateAfterLongPress = NAN;
_hasCustomActivationCriteria = NO;
#if !TARGET_OS_TV && !TARGET_OS_OSX
_realMinimumNumberOfTouches = self.minimumNumberOfTouches;
#endif
}
return self;
}
- (void)triggerAction
{
[_gestureHandler handleGesture:self];
}
#if !TARGET_OS_OSX
- (void)setMinimumNumberOfTouches:(NSUInteger)minimumNumberOfTouches
{
_realMinimumNumberOfTouches = minimumNumberOfTouches;
}
#endif
- (void)activateAfterLongPress
{
self.state = UIGestureRecognizerStateBegan;
// Send event in ACTIVE state because UIGestureRecognizerStateBegan is mapped to RNGestureHandlerStateBegan
[_gestureHandler handleGesture:self inState:RNGestureHandlerStateActive];
}
- (void)interactionsBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (touches.count == 0) {
[_gestureHandler reset];
}
#if !TARGET_OS_TV && !TARGET_OS_OSX
if (_hasCustomActivationCriteria) {
// We use "minimumNumberOfTouches" property to prevent pan handler from recognizing
// the gesture too early before we are sure that all criteria (e.g. minimum distance
// etc. are met)
super.minimumNumberOfTouches = 20;
} else {
super.minimumNumberOfTouches = _realMinimumNumberOfTouches;
}
#endif
#if TARGET_OS_OSX
[super mouseDown:event];
#else
[super touchesBegan:touches withEvent:event];
#endif
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
[self triggerAction];
if (!isnan(_activateAfterLongPress)) {
[self performSelector:@selector(activateAfterLongPress) withObject:nil afterDelay:_activateAfterLongPress];
}
}
- (void)interactionsMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
if (self.state == UIGestureRecognizerStatePossible && [self shouldFailUnderCustomCriteria]) {
self.state = UIGestureRecognizerStateFailed;
[self triggerAction];
return;
}
if ((self.state == UIGestureRecognizerStatePossible || self.state == UIGestureRecognizerStateChanged)) {
if (_gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]) {
// If the previous recognizer state is UIGestureRecognizerStateChanged
// then UIGestureRecognizer's sate machine will only transition to
// UIGestureRecognizerStateCancelled even if you set the state to
// UIGestureRecognizerStateFailed here. Making the behavior explicit.
self.state = (self.state == UIGestureRecognizerStatePossible) ? UIGestureRecognizerStateFailed
: UIGestureRecognizerStateCancelled;
[self reset];
return;
}
}
#if !TARGET_OS_TV && !TARGET_OS_OSX
if (_hasCustomActivationCriteria && self.state == UIGestureRecognizerStatePossible &&
[self shouldActivateUnderCustomCriteria]) {
super.minimumNumberOfTouches = _realMinimumNumberOfTouches;
if ([self numberOfTouches] >= _realMinimumNumberOfTouches) {
self.state = UIGestureRecognizerStateBegan;
[self setTranslation:CGPointMake(0, 0) inView:self.view];
}
}
#endif
}
- (void)interactionsEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
}
- (void)interactionsCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
}
#if TARGET_OS_OSX
- (void)mouseDown:(NSEvent *)event
{
[_gestureHandler setCurrentPointerTypeToMouse];
// super call was moved to interactionsBegan method to keep the
// original order of calls
[self interactionsBegan:[NSSet setWithObject:event] withEvent:event];
}
- (void)mouseDragged:(NSEvent *)event
{
[super mouseDragged:event];
[self interactionsMoved:[NSSet setWithObject:event] withEvent:event];
}
- (void)mouseUp:(NSEvent *)event
{
[super mouseUp:event];
[self interactionsEnded:[NSSet setWithObject:event] withEvent:event];
}
#else
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
// super call was moved to interactionsBegan method to keep the
// original order of calls
[self interactionsBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[self interactionsMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self interactionsEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self interactionsCancelled:touches withEvent:event];
}
#endif
- (void)reset
{
[self triggerAction];
[_gestureHandler.pointerTracker reset];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(activateAfterLongPress) object:nil];
self.enabled = YES;
[super reset];
[_gestureHandler reset];
}
- (void)updateHasCustomActivationCriteria
{
_hasCustomActivationCriteria = !isnan(_minDistSq) || !isnan(_minVelocityX) || !isnan(_minVelocityY) ||
!isnan(_minVelocitySq) || !isnan(_activeOffsetXStart) || !isnan(_activeOffsetXEnd) ||
!isnan(_activeOffsetYStart) || !isnan(_activeOffsetYEnd);
}
- (BOOL)shouldFailUnderCustomCriteria
{
#if TARGET_OS_OSX
CGPoint trans = [self translationInView:self.view.window.contentView];
#else
CGPoint trans = [self translationInView:self.view.window];
#endif
// Apple docs say that 10 units is the default allowable movement for UILongPressGestureRecognizer
if (!isnan(_activateAfterLongPress) && trans.x * trans.x + trans.y * trans.y > 100) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(activateAfterLongPress) object:nil];
return YES;
}
if (!isnan(_failOffsetXStart) && trans.x < _failOffsetXStart) {
return YES;
}
if (!isnan(_failOffsetXEnd) && trans.x > _failOffsetXEnd) {
return YES;
}
if (!isnan(_failOffsetYStart) && trans.y < _failOffsetYStart) {
return YES;
}
if (!isnan(_failOffsetYEnd) && trans.y > _failOffsetYEnd) {
return YES;
}
return NO;
}
- (BOOL)shouldActivateUnderCustomCriteria
{
#if TARGET_OS_OSX
CGPoint trans = [self translationInView:self.view.window.contentView];
#else
CGPoint trans = [self translationInView:self.view.window];
#endif
if (!isnan(_activeOffsetXStart) && trans.x < _activeOffsetXStart) {
return YES;
}
if (!isnan(_activeOffsetXEnd) && trans.x > _activeOffsetXEnd) {
return YES;
}
if (!isnan(_activeOffsetYStart) && trans.y < _activeOffsetYStart) {
return YES;
}
if (!isnan(_activeOffsetYEnd) && trans.y > _activeOffsetYEnd) {
return YES;
}
if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ(trans), _minDistSq)) {
return YES;
}
CGPoint velocity = [self velocityInView:self.view];
if (TEST_MIN_IF_NOT_NAN(velocity.x, _minVelocityX)) {
return YES;
}
if (TEST_MIN_IF_NOT_NAN(velocity.y, _minVelocityY)) {
return YES;
}
if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ(velocity), _minVelocitySq)) {
return YES;
}
return NO;
}
@end
@implementation RNPanGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNBetterPanGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)resetConfig
{
[super resetConfig];
RNBetterPanGestureRecognizer *recognizer = (RNBetterPanGestureRecognizer *)_recognizer;
recognizer.minVelocityX = NAN;
recognizer.minVelocityY = NAN;
recognizer.activeOffsetXStart = NAN;
recognizer.activeOffsetXEnd = NAN;
recognizer.failOffsetXStart = NAN;
recognizer.failOffsetXEnd = NAN;
recognizer.activeOffsetYStart = NAN;
recognizer.activeOffsetYEnd = NAN;
recognizer.failOffsetYStart = NAN;
recognizer.failOffsetYStart = NAN;
recognizer.failOffsetYEnd = NAN;
#if !TARGET_OS_OSX && !TARGET_OS_TV && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130400
if (@available(iOS 13.4, *)) {
recognizer.allowedScrollTypesMask = 0;
}
#endif
#if !TARGET_OS_OSX && !TARGET_OS_TV
recognizer.minimumNumberOfTouches = 1;
recognizer.maximumNumberOfTouches = NSUIntegerMax;
#endif
recognizer.minDistSq = NAN;
recognizer.minVelocitySq = NAN;
recognizer.activateAfterLongPress = NAN;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
RNBetterPanGestureRecognizer *recognizer = (RNBetterPanGestureRecognizer *)_recognizer;
APPLY_FLOAT_PROP(minVelocityX);
APPLY_FLOAT_PROP(minVelocityY);
APPLY_FLOAT_PROP(activeOffsetXStart);
APPLY_FLOAT_PROP(activeOffsetXEnd);
APPLY_FLOAT_PROP(failOffsetXStart);
APPLY_FLOAT_PROP(failOffsetXEnd);
APPLY_FLOAT_PROP(activeOffsetYStart);
APPLY_FLOAT_PROP(activeOffsetYEnd);
APPLY_FLOAT_PROP(failOffsetYStart);
APPLY_FLOAT_PROP(failOffsetYEnd);
#if !TARGET_OS_OSX && !TARGET_OS_TV && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130400
if (@available(iOS 13.4, *)) {
bool enableTrackpadTwoFingerGesture = [RCTConvert BOOL:config[@"enableTrackpadTwoFingerGesture"]];
if (enableTrackpadTwoFingerGesture) {
recognizer.allowedScrollTypesMask = UIScrollTypeMaskAll;
}
}
APPLY_NAMED_INT_PROP(minimumNumberOfTouches, @"minPointers");
APPLY_NAMED_INT_PROP(maximumNumberOfTouches, @"maxPointers");
#endif
id prop = config[@"minDist"];
if (prop != nil) {
CGFloat dist = [RCTConvert CGFloat:prop];
recognizer.minDistSq = dist * dist;
}
prop = config[@"minVelocity"];
if (prop != nil) {
CGFloat velocity = [RCTConvert CGFloat:prop];
recognizer.minVelocitySq = velocity * velocity;
}
prop = config[@"activateAfterLongPress"];
if (prop != nil) {
recognizer.activateAfterLongPress = [RCTConvert CGFloat:prop] / 1000.0;
recognizer.minDistSq = MAX(100, recognizer.minDistSq);
}
[recognizer updateHasCustomActivationCriteria];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
RNGestureHandlerState savedState = _lastState;
BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer];
_lastState = savedState;
return shouldBegin;
}
#if TARGET_OS_OSX
- (RNGestureHandlerEventExtraData *)eventExtraData:(NSPanGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forPan:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window.contentView]
withTranslation:[recognizer translationInView:recognizer.view.window.contentView]
withVelocity:[recognizer velocityInView:recognizer.view.window.contentView]
withNumberOfTouches:1
withPointerType:RNGestureHandlerMouse];
}
#else
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIPanGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forPan:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withTranslation:[recognizer translationInView:recognizer.view.window]
withVelocity:[recognizer velocityInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches
withPointerType:_pointerType];
}
#endif
@end

View File

@@ -0,0 +1,12 @@
//
// RNPinchHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNPinchGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,186 @@
//
// RNPinchHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNPinchHandler.h"
#if !TARGET_OS_TV
#if TARGET_OS_OSX
@interface RNBetterPinchRecognizer : NSMagnificationGestureRecognizer {
CGFloat prevMagnification;
NSTimeInterval prevTime;
}
@property (nonatomic, readonly) CGFloat velocity;
#else
@interface RNBetterPinchRecognizer : UIPinchGestureRecognizer
#endif
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@end
@implementation RNBetterPinchRecognizer {
__weak RNGestureHandler *_gestureHandler;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:self action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
}
#if TARGET_OS_OSX
prevMagnification = 0;
prevTime = 0;
#endif
return self;
}
- (void)handleGesture:(UIGestureRecognizer *)recognizer
{
if (self.state == UIGestureRecognizerStateBegan) {
#if TARGET_OS_OSX
self.magnification = 1;
#else
self.scale = 1;
#endif
}
[_gestureHandler handleGesture:recognizer];
}
- (void)interactionsBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
}
- (void)interactionsMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
}
- (void)interactionsEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
}
- (void)interactionsCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
}
#if TARGET_OS_OSX
- (void)magnifyWithEvent:(NSEvent *)event
{
[super magnifyWithEvent:event];
switch (self.state) {
case NSGestureRecognizerStateBegan:
[_gestureHandler setCurrentPointerTypeToMouse];
[self interactionsBegan:[NSSet setWithObject:event] withEvent:event];
break;
case NSGestureRecognizerStateChanged:
[self interactionsMoved:[NSSet setWithObject:event] withEvent:event];
break;
case NSGestureRecognizerStateEnded:
[self interactionsEnded:[NSSet setWithObject:event] withEvent:event];
break;
case NSGestureRecognizerStateCancelled:
[self interactionsCancelled:[NSSet setWithObject:event] withEvent:event];
break;
}
_velocity = (self.magnification - prevMagnification) / ((event.timestamp - prevTime) * 1000);
prevMagnification = self.magnification;
prevTime = event.timestamp;
}
#else
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
[super touchesBegan:touches withEvent:event];
[self interactionsBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[self interactionsMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self interactionsEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self interactionsCancelled:touches withEvent:event];
}
#endif
- (void)reset
{
[_gestureHandler.pointerTracker reset];
[super reset];
[_gestureHandler reset];
}
@end
#endif
@implementation RNPinchGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
#if !TARGET_OS_TV
_recognizer = [[RNBetterPinchRecognizer alloc] initWithGestureHandler:self];
#endif
}
return self;
}
#if !TARGET_OS_TV
#if TARGET_OS_OSX
- (RNGestureHandlerEventExtraData *)eventExtraData:(NSMagnificationGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forPinch:recognizer.magnification
withFocalPoint:[recognizer locationInView:recognizer.view]
withVelocity:((RNBetterPinchRecognizer *)recognizer).velocity
withNumberOfTouches:2
withPointerType:RNGestureHandlerMouse];
}
#else
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIPinchGestureRecognizer *)recognizer
{
CGPoint accumulatedPoint = CGPointZero;
for (int i = 0; i < recognizer.numberOfTouches; i++) {
CGPoint location = [recognizer locationOfTouch:i inView:recognizer.view];
accumulatedPoint.x += location.x;
accumulatedPoint.y += location.y;
}
CGPoint focalPoint =
CGPointMake(accumulatedPoint.x / recognizer.numberOfTouches, accumulatedPoint.y / recognizer.numberOfTouches);
return [RNGestureHandlerEventExtraData forPinch:recognizer.scale
withFocalPoint:focalPoint
withVelocity:recognizer.velocity
withNumberOfTouches:recognizer.numberOfTouches
withPointerType:_pointerType];
}
#endif
#endif // !TARGET_OS_TV
@end

View File

@@ -0,0 +1,12 @@
//
// RNRotationHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNRotationGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,169 @@
//
// RNRotationHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNRotationHandler.h"
#if !TARGET_OS_TV
#if TARGET_OS_OSX
@interface RNBetterRotationRecognizer : NSRotationGestureRecognizer {
CGFloat prevRotation;
NSTimeInterval prevTime;
}
@property (nonatomic, readonly) CGFloat velocity;
#else
@interface RNBetterRotationRecognizer : UIRotationGestureRecognizer
#endif
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@end
@implementation RNBetterRotationRecognizer {
__weak RNGestureHandler *_gestureHandler;
}
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:self action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
}
#if TARGET_OS_OSX
prevRotation = 0;
prevTime = 0;
#endif
return self;
}
- (void)handleGesture:(UIGestureRecognizer *)recognizer
{
if (self.state == UIGestureRecognizerStateBegan) {
self.rotation = 0;
}
[_gestureHandler handleGesture:recognizer];
}
- (void)interactionsBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
}
- (void)interactionsMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
}
- (void)interactionsEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
}
- (void)interactionsCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
}
#if TARGET_OS_OSX
- (void)rotateWithEvent:(NSEvent *)event
{
[super rotateWithEvent:event];
switch (self.state) {
case NSGestureRecognizerStateBegan:
[_gestureHandler setCurrentPointerTypeToMouse];
[self interactionsBegan:[NSSet setWithObject:event] withEvent:event];
break;
case NSGestureRecognizerStateChanged:
[self interactionsMoved:[NSSet setWithObject:event] withEvent:event];
break;
case NSGestureRecognizerStateEnded:
[self interactionsEnded:[NSSet setWithObject:event] withEvent:event];
break;
case NSGestureRecognizerStateCancelled:
[self interactionsCancelled:[NSSet setWithObject:event] withEvent:event];
break;
}
_velocity = (self.rotation - prevRotation) / ((event.timestamp - prevTime) * 1000);
prevRotation = self.rotation;
prevTime = event.timestamp;
}
#else
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
[super touchesBegan:touches withEvent:event];
[self interactionsBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[self interactionsMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self interactionsEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self interactionsCancelled:touches withEvent:event];
}
#endif
- (void)reset
{
[_gestureHandler.pointerTracker reset];
[super reset];
[_gestureHandler reset];
}
@end
#endif
@implementation RNRotationGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
#if !TARGET_OS_TV
_recognizer = [[RNBetterRotationRecognizer alloc] initWithGestureHandler:self];
#endif
}
return self;
}
#if !TARGET_OS_TV
#if TARGET_OS_OSX
- (RNGestureHandlerEventExtraData *)eventExtraData:(NSRotationGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forRotation:recognizer.rotation
withAnchorPoint:[recognizer locationInView:recognizer.view]
withVelocity:((RNBetterRotationRecognizer *)recognizer).velocity
withNumberOfTouches:2
withPointerType:RNGestureHandlerMouse];
}
#else
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIRotationGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData forRotation:recognizer.rotation
withAnchorPoint:[recognizer locationInView:recognizer.view]
withVelocity:recognizer.velocity
withNumberOfTouches:recognizer.numberOfTouches
withPointerType:_pointerType];
}
#endif
#endif // !TARGET_OS_TV
@end

View File

@@ -0,0 +1,12 @@
//
// RNTapHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNTapGestureHandler : RNGestureHandler
@end

View File

@@ -0,0 +1,337 @@
//
// RNTapHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNTapHandler.h"
#if !TARGET_OS_OSX
#import <UIKit/UIGestureRecognizerSubclass.h>
#endif
#import <React/RCTConvert.h>
// 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.
@interface RNBetterTapGestureRecognizer : UIGestureRecognizer
@property (nonatomic) NSUInteger numberOfTaps;
@property (nonatomic) NSTimeInterval maxDelay;
@property (nonatomic) NSTimeInterval maxDuration;
@property (nonatomic) CGFloat maxDistSq;
@property (nonatomic) CGFloat maxDeltaX;
@property (nonatomic) CGFloat maxDeltaY;
@property (nonatomic) NSInteger minPointers;
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
@end
@implementation RNBetterTapGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
NSUInteger _tapsSoFar;
CGPoint _initPosition;
NSInteger _maxNumberOfTouches;
}
static const NSUInteger defaultNumberOfTaps = 1;
static const NSInteger defaultMinPointers = 1;
static const CGFloat defaultMaxDelay = 0.2;
static const NSTimeInterval defaultMaxDuration = 0.5;
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_tapsSoFar = 0;
_numberOfTaps = defaultNumberOfTaps;
_minPointers = defaultMinPointers;
_maxDelay = defaultMaxDelay;
_maxDuration = defaultMaxDuration;
_maxDeltaX = NAN;
_maxDeltaY = NAN;
_maxDistSq = NAN;
}
return self;
}
- (void)triggerAction
{
[_gestureHandler handleGesture:self];
}
- (void)cancel
{
self.enabled = NO;
}
- (void)interactionsBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
if (_tapsSoFar == 0) {
// this recognizer sends UNDETERMINED -> BEGAN state change event before gestureRecognizerShouldBegin
// is called (it resets the gesture handler), making it send whatever the last known state as oldState
// in the event. If we reset it here it correctly sends UNDETERMINED as oldState.
[_gestureHandler reset];
#if TARGET_OS_OSX
_initPosition = [self locationInView:self.view.window.contentView];
#else
_initPosition = [self locationInView:self.view.window];
#endif
}
_tapsSoFar++;
if (_tapsSoFar) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancel) object:nil];
}
NSInteger numberOfTouches = [touches count];
if (numberOfTouches > _maxNumberOfTouches) {
_maxNumberOfTouches = numberOfTouches;
}
if (!isnan(_maxDuration)) {
[self performSelector:@selector(cancel) withObject:nil afterDelay:_maxDuration];
}
self.state = UIGestureRecognizerStatePossible;
[self triggerAction];
}
- (void)interactionsMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
NSInteger numberOfTouches = [touches count];
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];
}
- (void)interactionsEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
if (_numberOfTaps == _tapsSoFar && _maxNumberOfTouches >= _minPointers) {
self.state = UIGestureRecognizerStateEnded;
[self reset];
} else {
[self performSelector:@selector(cancel) withObject:nil afterDelay:_maxDelay];
}
}
- (void)interactionsCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateCancelled;
[self reset];
}
#if TARGET_OS_OSX
- (void)mouseDown:(NSEvent *)event
{
[_gestureHandler setCurrentPointerTypeToMouse];
[super mouseDown:event];
[self interactionsBegan:[NSSet setWithObject:event] withEvent:event];
}
- (void)rightMouseDown:(NSEvent *)event
{
[super rightMouseDown:event];
[self interactionsBegan:[NSSet setWithObject:event] withEvent:event];
}
- (void)mouseDragged:(NSEvent *)event
{
[super mouseDragged:event];
[self interactionsMoved:[NSSet setWithObject:event] withEvent:event];
}
- (void)rightMouseDragged:(NSEvent *)event
{
[super rightMouseDragged:event];
[self interactionsMoved:[NSSet setWithObject:event] withEvent:event];
}
- (void)mouseUp:(NSEvent *)event
{
[super mouseUp:event];
[self interactionsEnded:[NSSet setWithObject:event] withEvent:event];
}
- (void)rightMouseUp:(NSEvent *)event
{
[super rightMouseUp:event];
[self interactionsEnded:[NSSet setWithObject:event] withEvent:event];
}
#else
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
[super touchesBegan:touches withEvent:event];
[self interactionsBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[self interactionsMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self interactionsEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self interactionsCancelled:touches withEvent:event];
}
#endif
- (CGPoint)translationInView
{
#if TARGET_OS_OSX
CGPoint currentPosition = [self locationInView:self.view.window.contentView];
#else
CGPoint currentPosition = [self locationInView:self.view.window];
#endif
return CGPointMake(currentPosition.x - _initPosition.x, currentPosition.y - _initPosition.y);
}
- (BOOL)shouldFailUnderCustomCriteria
{
if (_gestureHandler.shouldCancelWhenOutside) {
if (![_gestureHandler containsPointInView]) {
return YES;
}
}
CGPoint trans = [self translationInView];
if (TEST_MAX_IF_NOT_NAN(fabs(trans.x), _maxDeltaX)) {
return YES;
}
if (TEST_MAX_IF_NOT_NAN(fabs(trans.y), _maxDeltaY)) {
return YES;
}
if (TEST_MAX_IF_NOT_NAN(fabs(trans.y * trans.y + trans.x * trans.x), _maxDistSq)) {
return YES;
}
return NO;
}
- (void)reset
{
if (self.state == UIGestureRecognizerStateFailed) {
[self triggerAction];
}
[_gestureHandler.pointerTracker reset];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancel) object:nil];
_tapsSoFar = 0;
_maxNumberOfTouches = 0;
self.enabled = YES;
[super reset];
[_gestureHandler reset];
}
@end
@implementation RNTapGestureHandler {
RNGestureHandlerEventExtraData *_lastData;
}
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNBetterTapGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)resetConfig
{
[super resetConfig];
RNBetterTapGestureRecognizer *recognizer = (RNBetterTapGestureRecognizer *)_recognizer;
recognizer.numberOfTaps = defaultNumberOfTaps;
recognizer.minPointers = defaultMinPointers;
recognizer.maxDeltaX = NAN;
recognizer.maxDeltaY = NAN;
recognizer.maxDelay = defaultMaxDelay;
recognizer.maxDuration = defaultMaxDuration;
recognizer.maxDistSq = NAN;
}
- (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:(UIGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded) {
return _lastData;
}
_lastData = [super eventExtraData:recognizer];
return _lastData;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// UNDETERMINED -> BEGAN state change event is sent before this method is called,
// in RNGestureHandler it resets _lastSatate variable, making is seem like handler
// went from UNDETERMINED to BEGAN and then from UNDETERMINED to ACTIVE.
// This way we preserve _lastState between events and keep correct state flow.
RNGestureHandlerState savedState = _lastState;
BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer];
_lastState = savedState;
return shouldBegin;
}
@end