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