1#import "RNForceTouchHandler.h"
2
3#import <UIKit/UIGestureRecognizerSubclass.h>
4
5#import <React/RCTConvert.h>
6
7@interface RNForceTouchGestureRecognizer : UIGestureRecognizer
8
9@property (nonatomic) CGFloat maxForce;
10@property (nonatomic) CGFloat minForce;
11@property (nonatomic) CGFloat force;
12@property (nonatomic) BOOL feedbackOnActivation;
13
14- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
15
16@end
17
18@implementation RNForceTouchGestureRecognizer {
19  __weak RNGestureHandler *_gestureHandler;
20  UITouch *_firstTouch;
21}
22
23static const CGFloat defaultForce = 0;
24static const CGFloat defaultMinForce = 0.2;
25static const CGFloat defaultMaxForce = NAN;
26static const BOOL defaultFeedbackOnActivation = NO;
27
28- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
29{
30  if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
31    _gestureHandler = gestureHandler;
32    _force = defaultForce;
33    _minForce = defaultMinForce;
34    _maxForce = defaultMaxForce;
35    _feedbackOnActivation = defaultFeedbackOnActivation;
36  }
37  return self;
38}
39
40- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
41{
42  if (_firstTouch) {
43    // ignore rest of fingers
44    return;
45  }
46  [super touchesBegan:touches withEvent:event];
47  [_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
48
49  _firstTouch = [touches anyObject];
50  [self handleForceWithTouches:touches];
51  self.state = UIGestureRecognizerStatePossible;
52}
53
54- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
55{
56  if (![touches containsObject:_firstTouch]) {
57    // Considered only the very first touch
58    return;
59  }
60  [super touchesMoved:touches withEvent:event];
61  [_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];
62
63  [self handleForceWithTouches:touches];
64
65  if ([self shouldFail]) {
66    self.state = UIGestureRecognizerStateFailed;
67    return;
68  }
69
70  if (self.state == UIGestureRecognizerStatePossible && [self shouldActivate]) {
71    [self performFeedbackIfRequired];
72    self.state = UIGestureRecognizerStateBegan;
73  }
74}
75
76- (BOOL)shouldActivate
77{
78  return (_force >= _minForce);
79}
80
81- (BOOL)shouldFail
82{
83  return TEST_MAX_IF_NOT_NAN(_force, _maxForce);
84}
85
86- (void)performFeedbackIfRequired
87{
88#if !TARGET_OS_TV
89  if (_feedbackOnActivation) {
90    if (@available(iOS 10.0, *)) {
91      [[[UIImpactFeedbackGenerator alloc] initWithStyle:(UIImpactFeedbackStyleMedium)] impactOccurred];
92    }
93  }
94#endif
95}
96
97- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
98{
99  if (![touches containsObject:_firstTouch]) {
100    // Considered only the very first touch
101    return;
102  }
103  [super touchesEnded:touches withEvent:event];
104  [_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
105  if (self.state == UIGestureRecognizerStateBegan || self.state == UIGestureRecognizerStateChanged) {
106    self.state = UIGestureRecognizerStateEnded;
107  } else {
108    self.state = UIGestureRecognizerStateFailed;
109  }
110}
111
112- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
113{
114  [super touchesCancelled:touches withEvent:event];
115  [_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
116}
117
118- (void)handleForceWithTouches:(NSSet<UITouch *> *)touches
119{
120  _force = _firstTouch.force / _firstTouch.maximumPossibleForce;
121}
122
123- (void)reset
124{
125  [_gestureHandler.pointerTracker reset];
126  [super reset];
127  _force = 0;
128  _firstTouch = NULL;
129}
130
131@end
132
133@implementation RNForceTouchHandler
134
135- (instancetype)initWithTag:(NSNumber *)tag
136{
137  if ((self = [super initWithTag:tag])) {
138    _recognizer = [[RNForceTouchGestureRecognizer alloc] initWithGestureHandler:self];
139  }
140  return self;
141}
142
143- (void)resetConfig
144{
145  [super resetConfig];
146  RNForceTouchGestureRecognizer *recognizer = (RNForceTouchGestureRecognizer *)_recognizer;
147
148  recognizer.feedbackOnActivation = defaultFeedbackOnActivation;
149  recognizer.maxForce = defaultMaxForce;
150  recognizer.minForce = defaultMinForce;
151}
152
153- (void)configure:(NSDictionary *)config
154{
155  [super configure:config];
156  RNForceTouchGestureRecognizer *recognizer = (RNForceTouchGestureRecognizer *)_recognizer;
157
158  APPLY_FLOAT_PROP(maxForce);
159  APPLY_FLOAT_PROP(minForce);
160
161  id prop = config[@"feedbackOnActivation"];
162  if (prop != nil) {
163    recognizer.feedbackOnActivation = [RCTConvert BOOL:prop];
164  }
165}
166
167- (RNGestureHandlerEventExtraData *)eventExtraData:(RNForceTouchGestureRecognizer *)recognizer
168{
169  return [RNGestureHandlerEventExtraData forForce:recognizer.force
170                                      forPosition:[recognizer locationInView:recognizer.view]
171                             withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
172                              withNumberOfTouches:recognizer.numberOfTouches];
173}
174
175@end
176