1// 2// RNNativeViewHandler.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 "RNNativeViewHandler.h" 10 11#import <UIKit/UIGestureRecognizerSubclass.h> 12 13#import <React/RCTConvert.h> 14#import <React/UIView+React.h> 15 16#ifdef RCT_NEW_ARCH_ENABLED 17#import <React/RCTScrollViewComponentView.h> 18#else 19#import <React/RCTScrollView.h> 20#endif // RCT_NEW_ARCH_ENABLED 21 22#pragma mark RNDummyGestureRecognizer 23 24@implementation RNDummyGestureRecognizer { 25 __weak RNGestureHandler *_gestureHandler; 26} 27 28- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler 29{ 30 if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) { 31 _gestureHandler = gestureHandler; 32 } 33 return self; 34} 35 36- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 37{ 38 [_gestureHandler.pointerTracker touchesBegan:touches withEvent:event]; 39} 40 41- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 42{ 43 [_gestureHandler.pointerTracker touchesMoved:touches withEvent:event]; 44} 45 46- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 47{ 48 [_gestureHandler.pointerTracker touchesEnded:touches withEvent:event]; 49 self.state = UIGestureRecognizerStateFailed; 50 [self reset]; 51} 52 53- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 54{ 55 [_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event]; 56 self.state = UIGestureRecognizerStateCancelled; 57 [self reset]; 58} 59 60- (void)reset 61{ 62 [_gestureHandler.pointerTracker reset]; 63 [super reset]; 64} 65 66@end 67 68#pragma mark RNNativeViewgestureHandler 69 70@implementation RNNativeViewGestureHandler { 71 BOOL _shouldActivateOnStart; 72 BOOL _disallowInterruption; 73} 74 75- (instancetype)initWithTag:(NSNumber *)tag 76{ 77 if ((self = [super initWithTag:tag])) { 78 _recognizer = [[RNDummyGestureRecognizer alloc] initWithGestureHandler:self]; 79 } 80 return self; 81} 82 83- (void)configure:(NSDictionary *)config 84{ 85 [super configure:config]; 86 _shouldActivateOnStart = [RCTConvert BOOL:config[@"shouldActivateOnStart"]]; 87 _disallowInterruption = [RCTConvert BOOL:config[@"disallowInterruption"]]; 88} 89 90- (void)bindToView:(UIView *)view 91{ 92 // For UIControl based views (UIButton, UISwitch) we provide special handling that would allow 93 // for properties like `disallowInterruption` to work. 94 if ([view isKindOfClass:[UIControl class]]) { 95 UIControl *control = (UIControl *)view; 96 [control addTarget:self action:@selector(handleTouchDown:forEvent:) forControlEvents:UIControlEventTouchDown]; 97 [control addTarget:self 98 action:@selector(handleTouchUpOutside:forEvent:) 99 forControlEvents:UIControlEventTouchUpOutside]; 100 [control addTarget:self 101 action:@selector(handleTouchUpInside:forEvent:) 102 forControlEvents:UIControlEventTouchUpInside]; 103 [control addTarget:self action:@selector(handleDragExit:forEvent:) forControlEvents:UIControlEventTouchDragExit]; 104 [control addTarget:self action:@selector(handleDragEnter:forEvent:) forControlEvents:UIControlEventTouchDragEnter]; 105 [control addTarget:self action:@selector(handleTouchCancel:forEvent:) forControlEvents:UIControlEventTouchCancel]; 106 } else { 107 [super bindToView:view]; 108 } 109 110 // We can restore default scrollview behaviour to delay touches to scrollview's children 111 // because gesture handler system can handle cancellation of scroll recognizer when JS responder 112 // is set 113#ifdef RCT_NEW_ARCH_ENABLED 114 if ([view isKindOfClass:[RCTScrollViewComponentView class]]) { 115 UIScrollView *scrollView = ((RCTScrollViewComponentView *)view).scrollView; 116 scrollView.delaysContentTouches = YES; 117 } 118#else 119 if ([view isKindOfClass:[RCTScrollView class]]) { 120 // This part of the code is coupled with RN implementation of ScrollView native wrapper and 121 // we expect for RCTScrollView component to contain a subclass of UIScrollview as the only 122 // subview 123 UIScrollView *scrollView = [view.subviews objectAtIndex:0]; 124 scrollView.delaysContentTouches = YES; 125 } 126#endif // RCT_NEW_ARCH_ENABLED 127} 128 129- (void)handleTouchDown:(UIView *)sender forEvent:(UIEvent *)event 130{ 131 [self reset]; 132 133 if (_disallowInterruption) { 134 // When `disallowInterruption` is set we cancel all gesture handlers when this UIControl 135 // gets DOWN event 136 for (UITouch *touch in [event allTouches]) { 137 for (UIGestureRecognizer *recogn in [touch gestureRecognizers]) { 138 recogn.enabled = NO; 139 recogn.enabled = YES; 140 } 141 } 142 } 143 144 [self sendEventsInState:RNGestureHandlerStateActive 145 forViewWithTag:sender.reactTag 146 withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES]]; 147} 148 149- (void)handleTouchUpOutside:(UIView *)sender forEvent:(UIEvent *)event 150{ 151 [self sendEventsInState:RNGestureHandlerStateEnd 152 forViewWithTag:sender.reactTag 153 withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]]; 154} 155 156- (void)handleTouchUpInside:(UIView *)sender forEvent:(UIEvent *)event 157{ 158 [self sendEventsInState:RNGestureHandlerStateEnd 159 forViewWithTag:sender.reactTag 160 withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES]]; 161} 162 163- (void)handleDragExit:(UIView *)sender forEvent:(UIEvent *)event 164{ 165 // Pointer is moved outside of the view bounds, we cancel button when `shouldCancelWhenOutside` is set 166 if (self.shouldCancelWhenOutside) { 167 UIControl *control = (UIControl *)sender; 168 [control cancelTrackingWithEvent:event]; 169 [self sendEventsInState:RNGestureHandlerStateEnd 170 forViewWithTag:sender.reactTag 171 withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]]; 172 } else { 173 [self sendEventsInState:RNGestureHandlerStateActive 174 forViewWithTag:sender.reactTag 175 withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]]; 176 } 177} 178 179- (void)handleDragEnter:(UIView *)sender forEvent:(UIEvent *)event 180{ 181 [self sendEventsInState:RNGestureHandlerStateActive 182 forViewWithTag:sender.reactTag 183 withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES]]; 184} 185 186- (void)handleTouchCancel:(UIView *)sender forEvent:(UIEvent *)event 187{ 188 [self sendEventsInState:RNGestureHandlerStateCancelled 189 forViewWithTag:sender.reactTag 190 withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]]; 191} 192 193@end 194