diff --git a/packages/react-native-gesture-handler/src/v3/components/StatefulPressable.tsx b/packages/react-native-gesture-handler/src/v3/components/StatefulPressable.tsx index bfbf25608e..e34332e5ae 100644 --- a/packages/react-native-gesture-handler/src/v3/components/StatefulPressable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/StatefulPressable.tsx @@ -91,6 +91,7 @@ const StatefulPressable = (props: PressableProps) => { const isOnPressAllowed = useRef(true); const jsResponderContext = use(JSResponderContext); const isCurrentlyPressed = useRef(false); + const pressInEventRef = useRef(null); const dimensions = useRef({ width: 0, height: 0, @@ -187,6 +188,7 @@ const StatefulPressable = (props: PressableProps) => { } isCurrentlyPressed.current = true; + pressInEventRef.current = event; if (unstable_pressDelay) { pressDelayTimeoutRef.current = setTimeout(() => { innerHandlePressIn(event); @@ -400,6 +402,21 @@ const StatefulPressable = (props: PressableProps) => { event.canceled ? StateMachineEvent.CANCEL : StateMachineEvent.FINALIZE ); + // On iOS the button's cancel can land before the long press's touch + // cancel, or without one, so deliver the press-out here + if ( + Platform.OS === 'ios' && + event.canceled && + isCurrentlyPressed.current + ) { + handlePressOut( + pressInEventRef.current ?? + viewCenterToPressableEvent(dimensions.current), + false + ); + return; + } + handleFinalize(); }, enabled: disabled !== true,