Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@

#import <React/RCTConvert.h>
#import <React/RCTScrollViewComponentView.h>
#import <React/RCTTextInputComponentView.h>
#import <React/UIView+React.h>

#if !TARGET_OS_OSX
@interface RNNativeViewGestureHandler ()
- (void)handleTextViewTouchDown:(UIEvent *)event;
- (void)handleTextViewTouchUp:(UIEvent *)event;
- (void)handleTextViewTouchCancel:(UIEvent *)event;
@end
#endif

#pragma mark RNDummyGestureRecognizer

@implementation RNDummyGestureRecognizer {
Expand All @@ -31,9 +40,19 @@ - (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
}

#if !TARGET_OS_OSX
- (BOOL)isAttachedToTextView
{
return [self.view isKindOfClass:[UITextView class]];
}

- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerTypeForEvent:event];

if ([self isAttachedToTextView]) {
[(RNNativeViewGestureHandler *)_gestureHandler handleTextViewTouchDown:event];
}

[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
}

Expand All @@ -46,6 +65,11 @@ - (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];

if ([self isAttachedToTextView]) {
[(RNNativeViewGestureHandler *)_gestureHandler handleTextViewTouchUp:event];
}

self.state = UIGestureRecognizerStateFailed;

// For now, we are handling only the scroll view case.
Expand All @@ -58,6 +82,11 @@ - (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];

if ([self isAttachedToTextView]) {
[(RNNativeViewGestureHandler *)_gestureHandler handleTextViewTouchCancel:event];
}

self.state = UIGestureRecognizerStateCancelled;
[self reset];
}
Expand Down Expand Up @@ -114,6 +143,9 @@ @implementation RNNativeViewGestureHandler {
BOOL _shouldActivateOnStart;
BOOL _disallowInterruption;
RNGestureHandlerEventExtraData *_lastActiveExtraData;
#if !TARGET_OS_OSX
__weak UIControl *_control;
#endif
}

- (instancetype)initWithTag:(NSNumber *)tag
Expand All @@ -135,39 +167,65 @@ - (void)updateConfig:(NSDictionary *)config

- (void)bindToView:(UIView *)view
{
UIView *textInputChild = nil;

// For UIControl based views (UIButton, UISwitch) we provide special handling that would allow
// for properties like `disallowInterruption` to work.
if ([view isKindOfClass:[UIControl class]]) {
UIControl *control = (UIControl *)view;
_control = (UIControl *)view;
} else if ([view isKindOfClass:[RCTTextInputComponentView class]]) {
// TextInput (RCTTextInputComponentView) contains a UITextField (single-line) or UITextView (multi-line) as a
// subview. UITextField is a UIControl, so we can use UIControl events. UITextView is not a UIControl, so we need to
// attach the gesture recognizer to it directly.
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UITextField class]]) {
_control = (UIControl *)subview;
break;
} else if ([subview isKindOfClass:[UITextView class]]) {
textInputChild = subview;
break;
}
}
}
Comment on lines +177 to +189
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to distinguish this? If we're already attaching the recognizer to RCTTextInputComponentView, can't we do that in all cases?


if (_control) {
// Pressing UISwitch triggers only touchUp and valueChanged callbacks. In order to align its behavior
// with other UIControls, we have to dispatch full Gesture Handler events flow in one callback, as
// touchesDown is not executed.
if ([view isKindOfClass:[UISwitch class]]) {
if ([_control isKindOfClass:[UISwitch class]]) {
_pointerType = RNGestureHandlerTouch;
[control addTarget:self action:@selector(handleSwitch:) forControlEvents:UIControlEventValueChanged];
[_control addTarget:self action:@selector(handleSwitch:) forControlEvents:UIControlEventValueChanged];
} else {
[control addTarget:self action:@selector(handleTouchDown:forEvent:) forControlEvents:UIControlEventTouchDown];
[control addTarget:self
[_control addTarget:self action:@selector(handleTouchDown:forEvent:) forControlEvents:UIControlEventTouchDown];
[_control addTarget:self
action:@selector(handleTouchUpOutside:forEvent:)
forControlEvents:UIControlEventTouchUpOutside];
[control addTarget:self
[_control addTarget:self
action:@selector(handleTouchUpInside:forEvent:)
forControlEvents:UIControlEventTouchUpInside];
[control addTarget:self action:@selector(handleDragExit:forEvent:) forControlEvents:UIControlEventTouchDragExit];
[control addTarget:self
[_control addTarget:self action:@selector(handleDragExit:forEvent:) forControlEvents:UIControlEventTouchDragExit];
[_control addTarget:self
action:@selector(handleDragInside:forEvent:)
forControlEvents:UIControlEventTouchDragInside];
[control addTarget:self
[_control addTarget:self
action:@selector(handleDragOutside:forEvent:)
forControlEvents:UIControlEventTouchDragOutside];
[control addTarget:self
[_control addTarget:self
action:@selector(handleDragEnter:forEvent:)
forControlEvents:UIControlEventTouchDragEnter];
[control addTarget:self action:@selector(handleTouchCancel:forEvent:) forControlEvents:UIControlEventTouchCancel];
[_control addTarget:self
action:@selector(handleTouchCancel:forEvent:)
forControlEvents:UIControlEventTouchCancel];
}
} else {
[super bindToView:view];
// For multiline TextInput (UITextView), bind to the child view so the recognizer receives
// touch events directly, then restore viewTag to the parent's react tag.
if (textInputChild != nil) {
[super bindToView:textInputChild];
self.viewTag = view.reactTag;
} else {
[super bindToView:view];
}
}

// We can restore default scrollview behaviour to delay touches to scrollview's children
Expand All @@ -181,8 +239,9 @@ - (void)unbindFromView
{
UIView *view = self.recognizer.view;

if ([view isKindOfClass:[UIControl class]]) {
[(UIControl *)view removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents];
if (_control) {
[_control removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents];
_control = nil;
}

// Restore the React Native's overriden behavor for not delaying content touches
Expand Down Expand Up @@ -223,6 +282,47 @@ - (void)handleSwitch:(UIView *)sender
[self reset];
}

- (void)handleTextViewTouchDown:(UIEvent *)event
{
[self reset];

RNGestureHandlerEventExtraData *extraData = [RNGestureHandlerEventExtraData forPointerInside:YES
withNumberOfTouches:event.allTouches.count
withPointerType:_pointerType];

[self sendEventsInState:RNGestureHandlerStateBegan forViewWithTag:self.viewTag withExtraData:extraData];
[self sendEventsInState:RNGestureHandlerStateActive forViewWithTag:self.viewTag withExtraData:extraData];
_lastActiveExtraData = extraData;
}

- (void)handleTextViewTouchUp:(UIEvent *)event
{
BOOL isInside = [self containsPointInView];

if (!isInside && self.shouldCancelWhenOutside) {
[self sendEventsInState:RNGestureHandlerStateFailed
forViewWithTag:self.viewTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO
withNumberOfTouches:event.allTouches.count
withPointerType:_pointerType]];
} else {
[self sendEventsInState:RNGestureHandlerStateEnd
forViewWithTag:self.viewTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:isInside
withNumberOfTouches:event.allTouches.count
withPointerType:_pointerType]];
}
}

- (void)handleTextViewTouchCancel:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateCancelled
forViewWithTag:self.viewTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO
withNumberOfTouches:event.allTouches.count
withPointerType:_pointerType]];
}

- (void)handleTouchDown:(UIView *)sender forEvent:(UIEvent *)event
{
[self setCurrentPointerTypeForEvent:event];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,11 @@ - (void)sendEvent
// it may happen that the gesture recognizer is reset after it's been unbound from the view,
// it that recognizer tried to send event, the app would crash because the target of the event
// would be nil.
if (_gestureHandler.recognizer.view.reactTag == nil &&
_gestureHandler.actionType != RNGestureHandlerActionTypeNativeDetector) {
if (_gestureHandler.viewTag == nil && _gestureHandler.actionType != RNGestureHandlerActionTypeNativeDetector) {
return;
}

[_gestureHandler sendTouchEventInState:[_gestureHandler state]
forViewWithTag:_gestureHandler.recognizer.view.reactTag];
[_gestureHandler sendTouchEventInState:[_gestureHandler state] forViewWithTag:_gestureHandler.viewTag];
}

@end
Loading