diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt index 4800c5aa1a..a254d9ac63 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt @@ -123,6 +123,10 @@ class NativeViewGestureHandler : GestureHandler() { hook.afterGestureEnd(event) } else if (state == STATE_UNDETERMINED || state == STATE_BEGAN) { + if (state != STATE_BEGAN && hook.canBegin(event)) { + begin() + } + when { shouldActivateOnStart -> { tryIntercept(view, event) @@ -138,12 +142,6 @@ class NativeViewGestureHandler : GestureHandler() { hook.wantsToHandleEventBeforeActivation() -> { hook.handleEventBeforeActivation(event) } - - state != STATE_BEGAN -> { - if (hook.canBegin(event)) { - begin() - } - } } } else if (state == STATE_ACTIVE) { hook.sendTouchEvent(view, event) diff --git a/packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm b/packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm index dd9251c267..842467d43f 100644 --- a/packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm +++ b/packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm @@ -146,6 +146,12 @@ - (void)bindToView:(UIView *)view action:@selector(handleTouchUpInside:forEvent:) forControlEvents:UIControlEventTouchUpInside]; [control addTarget:self action:@selector(handleDragExit:forEvent:) forControlEvents:UIControlEventTouchDragExit]; + [control addTarget:self + action:@selector(handleDragInside:forEvent:) + forControlEvents:UIControlEventTouchDragInside]; + [control addTarget:self + action:@selector(handleDragOutside:forEvent:) + forControlEvents:UIControlEventTouchDragOutside]; [control addTarget:self action:@selector(handleDragEnter:forEvent:) forControlEvents:UIControlEventTouchDragEnter]; [control addTarget:self action:@selector(handleTouchCancel:forEvent:) forControlEvents:UIControlEventTouchCancel]; } else { @@ -161,8 +167,14 @@ - (void)bindToView:(UIView *)view - (void)unbindFromView { + UIView *view = self.recognizer.view; + + if ([view isKindOfClass:[UIControl class]]) { + [(UIControl *)view removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents]; + } + // Restore the React Native's overriden behavor for not delaying content touches - UIScrollView *scrollView = [self retrieveScrollView:self.recognizer.view]; + UIScrollView *scrollView = [self retrieveScrollView:view]; scrollView.delaysContentTouches = NO; [super unbindFromView]; @@ -184,6 +196,12 @@ - (void)handleTouchDown:(UIView *)sender forEvent:(UIEvent *)event } } + [self sendEventsInState:RNGestureHandlerStateBegan + forViewWithTag:sender.reactTag + withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES + withNumberOfTouches:event.allTouches.count + withPointerType:_pointerType]]; + [self sendEventsInState:RNGestureHandlerStateActive forViewWithTag:sender.reactTag withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES @@ -211,22 +229,21 @@ - (void)handleTouchUpInside:(UIView *)sender forEvent:(UIEvent *)event - (void)handleDragExit:(UIView *)sender forEvent:(UIEvent *)event { + RNGestureHandlerState newState = RNGestureHandlerStateActive; + // Pointer is moved outside of the view bounds, we cancel button when `shouldCancelWhenOutside` is set if (self.shouldCancelWhenOutside) { UIControl *control = (UIControl *)sender; [control cancelTrackingWithEvent:event]; - [self sendEventsInState:RNGestureHandlerStateEnd - forViewWithTag:sender.reactTag - withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO - withNumberOfTouches:event.allTouches.count - withPointerType:_pointerType]]; - } else { - [self sendEventsInState:RNGestureHandlerStateActive - forViewWithTag:sender.reactTag - withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO - withNumberOfTouches:event.allTouches.count - withPointerType:_pointerType]]; + + newState = RNGestureHandlerStateEnd; } + + [self sendEventsInState:newState + forViewWithTag:sender.reactTag + withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO + withNumberOfTouches:event.allTouches.count + withPointerType:_pointerType]]; } - (void)handleDragEnter:(UIView *)sender forEvent:(UIEvent *)event @@ -238,6 +255,24 @@ - (void)handleDragEnter:(UIView *)sender forEvent:(UIEvent *)event withPointerType:_pointerType]]; } +- (void)handleDragInside:(UIView *)sender forEvent:(UIEvent *)event +{ + [self sendEventsInState:RNGestureHandlerStateActive + forViewWithTag:sender.reactTag + withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES + withNumberOfTouches:event.allTouches.count + withPointerType:_pointerType]]; +} + +- (void)handleDragOutside:(UIView *)sender forEvent:(UIEvent *)event +{ + [self sendEventsInState:RNGestureHandlerStateActive + forViewWithTag:sender.reactTag + withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO + withNumberOfTouches:event.allTouches.count + withPointerType:_pointerType]]; +} + - (void)handleTouchCancel:(UIView *)sender forEvent:(UIEvent *)event { [self sendEventsInState:RNGestureHandlerStateCancelled diff --git a/packages/react-native-gesture-handler/src/components/GestureButtons.tsx b/packages/react-native-gesture-handler/src/components/GestureButtons.tsx index 2832752605..4c9c3b4b34 100644 --- a/packages/react-native-gesture-handler/src/components/GestureButtons.tsx +++ b/packages/react-native-gesture-handler/src/components/GestureButtons.tsx @@ -66,12 +66,7 @@ class InnerBaseButton extends React.Component { this.props.onPress(pointerInside); } - if ( - !this.lastActive && - // NativeViewGestureHandler sends different events based on platform - state === (Platform.OS !== 'android' ? State.ACTIVE : State.BEGAN) && - pointerInside - ) { + if (!this.lastActive && state === State.BEGAN && pointerInside) { this.longPressDetected = false; if (this.props.onLongPress) { this.longPressTimeout = setTimeout( diff --git a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts index af4fb93057..89cf2bf1e9 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts +++ b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts @@ -57,7 +57,7 @@ function getIosStatesConfig( eventName: StateMachineEvent.LONG_PRESS_TOUCHES_DOWN, }, { - eventName: StateMachineEvent.NATIVE_START, + eventName: StateMachineEvent.NATIVE_BEGIN, callback: handlePressIn, }, { diff --git a/packages/react-native-gesture-handler/src/v3/components/GestureButtons.tsx b/packages/react-native-gesture-handler/src/v3/components/GestureButtons.tsx index a61a0a44b7..cec9b8cf10 100644 --- a/packages/react-native-gesture-handler/src/v3/components/GestureButtons.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/GestureButtons.tsx @@ -19,7 +19,7 @@ export const RawButton = createNativeWrapper< RawButtonProps >(GestureHandlerButton, { shouldCancelWhenOutside: false, - shouldActivateOnStart: false, + shouldActivateOnStart: true, }); export const BaseButton = (props: BaseButtonProps) => { @@ -38,28 +38,21 @@ export const BaseButton = (props: BaseButtonProps) => { }; const onBegin = (e: CallbackEventType) => { - if (Platform.OS === 'android' && e.pointerInside) { - longPressDetected.current = false; - if (onLongPress) { - longPressTimeout.current = setTimeout(wrappedLongPress, delayLongPress); - } - - props.onBegin?.(e); + if (!e.pointerInside) { + return; } - }; - const onActivate = (e: CallbackEventType) => { onActiveStateChange?.(true); - if (Platform.OS !== 'android' && e.pointerInside) { - longPressDetected.current = false; - if (onLongPress) { - longPressTimeout.current = setTimeout(wrappedLongPress, delayLongPress); - } - - props.onBegin?.(e); + longPressDetected.current = false; + if (onLongPress) { + longPressTimeout.current = setTimeout(wrappedLongPress, delayLongPress); } + props.onBegin?.(e); + }; + + const onActivate = (e: CallbackEventType) => { if (!e.pointerInside && longPressTimeout.current !== undefined) { clearTimeout(longPressTimeout.current); longPressTimeout.current = undefined; diff --git a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx index 245c250657..3d05214f5d 100644 --- a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx @@ -308,7 +308,7 @@ const Pressable = (props: PressableProps) => { stateMachine.handleEvent(StateMachineEvent.NATIVE_BEGIN); }, onActivate: () => { - if (Platform.OS !== 'android') { + if (Platform.OS !== 'android' && Platform.OS !== 'ios') { // Native.onActivate is broken with Android + hitSlop stateMachine.handleEvent(StateMachineEvent.NATIVE_START); } @@ -323,9 +323,7 @@ const Pressable = (props: PressableProps) => { success ? StateMachineEvent.FINALIZE : StateMachineEvent.CANCEL ); - if (Platform.OS !== 'ios') { - handleFinalize(); - } + handleFinalize(); }, enabled: disabled !== true, disableReanimated: true,