1//
2//  RNLongPressHandler.m
3//  RNGestureHandler
4//
5//  Created by Krzysztof Magiera on 12/10/2017.
6//  Copyright © 2017 Software Mansion. All rights reserved.
7//
8
9#import "RNLongPressHandler.h"
10
11#import <UIKit/UIGestureRecognizerSubclass.h>
12
13#import <React/RCTConvert.h>
14
15#import <mach/mach_time.h>
16
17@interface RNBetterLongPressGestureRecognizer : UILongPressGestureRecognizer {
18  uint64_t startTime;
19  uint64_t previousTime;
20}
21
22- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
23- (void)handleGesture:(UIGestureRecognizer *)recognizer;
24- (NSUInteger)getDuration;
25
26@end
27
28@implementation RNBetterLongPressGestureRecognizer {
29  __weak RNGestureHandler *_gestureHandler;
30  CGPoint _initPosition;
31}
32
33- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
34{
35  if ((self = [super initWithTarget:self action:@selector(handleGesture:)])) {
36    _gestureHandler = gestureHandler;
37  }
38  return self;
39}
40
41- (void)handleGesture:(UIGestureRecognizer *)recognizer
42{
43  previousTime = mach_absolute_time();
44  [_gestureHandler handleGesture:recognizer];
45}
46
47- (void)triggerAction
48{
49  [self handleGesture:self];
50}
51
52- (CGPoint)translationInView
53{
54  CGPoint currentPosition = [self locationInView:self.view];
55  return CGPointMake(currentPosition.x - _initPosition.x, currentPosition.y - _initPosition.y);
56}
57
58- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
59{
60  [super touchesBegan:touches withEvent:event];
61  [_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
62
63  _initPosition = [self locationInView:self.view];
64  startTime = mach_absolute_time();
65  [_gestureHandler reset];
66  [self triggerAction];
67}
68
69- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
70{
71  [super touchesMoved:touches withEvent:event];
72  [_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
73
74  CGPoint trans = [self translationInView];
75  if ((_gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]) ||
76      (TEST_MAX_IF_NOT_NAN(
77          fabs(trans.y * trans.y + trans.x + trans.x), self.allowableMovement * self.allowableMovement))) {
78    self.enabled = NO;
79    self.enabled = YES;
80  }
81}
82
83- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
84{
85  [super touchesEnded:touches withEvent:event];
86  [_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
87}
88
89- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
90{
91  [super touchesCancelled:touches withEvent:event];
92  [_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
93}
94
95- (void)reset
96{
97  if (self.state == UIGestureRecognizerStateFailed) {
98    [self triggerAction];
99  }
100
101  [_gestureHandler.pointerTracker reset];
102
103  [super reset];
104}
105
106- (NSUInteger)getDuration
107{
108  static mach_timebase_info_data_t sTimebaseInfo;
109
110  if (sTimebaseInfo.denom == 0) {
111    mach_timebase_info(&sTimebaseInfo);
112  }
113
114  return (NSUInteger)(((previousTime - startTime) * sTimebaseInfo.numer / (sTimebaseInfo.denom * 1000000)));
115}
116
117@end
118
119@implementation RNLongPressGestureHandler
120
121- (instancetype)initWithTag:(NSNumber *)tag
122{
123  if ((self = [super initWithTag:tag])) {
124    _recognizer = [[RNBetterLongPressGestureRecognizer alloc] initWithGestureHandler:self];
125  }
126  return self;
127}
128
129- (void)resetConfig
130{
131  [super resetConfig];
132  UILongPressGestureRecognizer *recognizer = (UILongPressGestureRecognizer *)_recognizer;
133
134  recognizer.minimumPressDuration = 0.5;
135  recognizer.allowableMovement = 10;
136}
137
138- (void)configure:(NSDictionary *)config
139{
140  [super configure:config];
141  UILongPressGestureRecognizer *recognizer = (UILongPressGestureRecognizer *)_recognizer;
142
143  id prop = config[@"minDurationMs"];
144  if (prop != nil) {
145    recognizer.minimumPressDuration = [RCTConvert CGFloat:prop] / 1000.0;
146  }
147
148  prop = config[@"maxDist"];
149  if (prop != nil) {
150    recognizer.allowableMovement = [RCTConvert CGFloat:prop];
151  }
152}
153
154- (RNGestureHandlerState)state
155{
156  // For long press recognizer we treat "Began" state as "active"
157  // as it changes its state to "Began" as soon as the the minimum
158  // hold duration timeout is reached, whereas state "Changed" is
159  // only set after "Began" phase if there is some movement.
160  if (_recognizer.state == UIGestureRecognizerStateBegan) {
161    return RNGestureHandlerStateActive;
162  }
163  return [super state];
164}
165
166- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
167{
168  // same as TapGH, this needs to be unified when all handlers are updated
169  RNGestureHandlerState savedState = _lastState;
170  BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer];
171  _lastState = savedState;
172
173  return shouldBegin;
174}
175
176- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
177{
178  return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
179                                withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
180                                 withNumberOfTouches:recognizer.numberOfTouches
181                                        withDuration:[(RNBetterLongPressGestureRecognizer *)recognizer getDuration]];
182}
183@end
184