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