Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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];
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,7 @@
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(
Expand Down Expand Up @@ -152,14 +147,14 @@
ref,
...props
}: Omit<LegacyBaseButtonProps, 'innerRef'> & {
ref?: React.ForwardedRef<React.ComponentType<any>> | undefined;

Check warning on line 150 in packages/react-native-gesture-handler/src/components/GestureButtons.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type
}) => <InnerBaseButton innerRef={ref} {...props} />;

const AnimatedBaseButton = ({
ref,
...props
}: Animated.AnimatedProps<BaseButtonWithRefProps> & {
ref?: React.ForwardedRef<React.ComponentType<any>> | undefined;

Check warning on line 157 in packages/react-native-gesture-handler/src/components/GestureButtons.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type
}) => <AnimatedInnerBaseButton innerRef={ref} {...props} />;

const btnStyles = StyleSheet.create({
Expand Down Expand Up @@ -187,7 +182,7 @@

private onActiveStateChange = (active: boolean) => {
if (Platform.OS !== 'android') {
this.opacity.setValue(active ? this.props.activeOpacity! : 0);

Check warning on line 185 in packages/react-native-gesture-handler/src/components/GestureButtons.tsx

View workflow job for this annotation

GitHub Actions / check

Forbidden non-null assertion
}

this.props.onActiveStateChange?.(active);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const LegacyPressable = (props: LegacyPressableProps) => {
const isOnPressAllowed = useRef<boolean>(true);
const isCurrentlyPressed = useRef<boolean>(false);
const dimensions = useRef<PressableDimensions>({ width: 0, height: 0 });
const lastTouchEvent = useRef<PressableEvent | null>(null);

const normalizedHitSlop: Insets = useMemo(
() =>
Expand Down Expand Up @@ -259,6 +260,7 @@ const LegacyPressable = (props: LegacyPressableProps) => {
.cancelsTouchesInView(false)
.onTouchesDown((event) => {
const pressableEvent = gestureTouchToPressableEvent(event);
lastTouchEvent.current = pressableEvent;
stateMachine.handleEvent(
StateMachineEvent.LONG_PRESS_TOUCHES_DOWN,
pressableEvent
Expand Down Expand Up @@ -303,7 +305,10 @@ const LegacyPressable = (props: LegacyPressableProps) => {
}
})
.onBegin(() => {
stateMachine.handleEvent(StateMachineEvent.NATIVE_BEGIN);
stateMachine.handleEvent(
StateMachineEvent.NATIVE_BEGIN,
lastTouchEvent.current ?? undefined
);
})
.onStart(() => {
if (Platform.OS !== 'android') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function getIosStatesConfig(
eventName: StateMachineEvent.LONG_PRESS_TOUCHES_DOWN,
},
{
eventName: StateMachineEvent.NATIVE_START,
eventName: StateMachineEvent.NATIVE_BEGIN,
callback: handlePressIn,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const RawButton = createNativeWrapper<
RawButtonProps
>(GestureHandlerButton, {
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
shouldActivateOnStart: true,
});

export const BaseButton = (props: BaseButtonProps) => {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const Pressable = (props: PressableProps) => {
width: 0,
height: 0,
});
const lastTouchEvent = useRef<PressableEvent | null>(null);

const normalizedHitSlop: Insets = useMemo(
() =>
Expand Down Expand Up @@ -257,6 +258,7 @@ const Pressable = (props: PressableProps) => {
cancelsTouchesInView: false,
onTouchesDown: (event) => {
const pressableEvent = gestureTouchToPressableEvent(event);
lastTouchEvent.current = pressableEvent;
stateMachine.handleEvent(
StateMachineEvent.LONG_PRESS_TOUCHES_DOWN,
pressableEvent
Expand Down Expand Up @@ -305,11 +307,15 @@ const Pressable = (props: PressableProps) => {
}
},
onBegin: () => {
stateMachine.handleEvent(StateMachineEvent.NATIVE_BEGIN);
stateMachine.handleEvent(
StateMachineEvent.NATIVE_BEGIN,
lastTouchEvent.current ?? undefined
);
},
onActivate: () => {
if (Platform.OS !== 'android') {
if (Platform.OS !== 'android' && Platform.OS !== 'ios') {
// Native.onActivate is broken with Android + hitSlop
// On iOS, onActivate fires on drag (not touch down), so we use onBegin + LONG_PRESS_TOUCHES_DOWN instead
Copy link
Member

Choose a reason for hiding this comment

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

Is this still true?

stateMachine.handleEvent(StateMachineEvent.NATIVE_START);
}
},
Expand All @@ -323,9 +329,7 @@ const Pressable = (props: PressableProps) => {
success ? StateMachineEvent.FINALIZE : StateMachineEvent.CANCEL
);

if (Platform.OS !== 'ios') {
handleFinalize();
}
handleFinalize();
},
enabled: disabled !== true,
disableReanimated: true,
Expand Down
Loading