Skip to content

[ios] fix pressable with intercepting detector#4041

Open
akwasniewski wants to merge 2 commits intomainfrom
@akwasniewski/ios-fix-buttons-with-virutal-detector
Open

[ios] fix pressable with intercepting detector#4041
akwasniewski wants to merge 2 commits intomainfrom
@akwasniewski/ios-fix-buttons-with-virutal-detector

Conversation

@akwasniewski
Copy link
Contributor

@akwasniewski akwasniewski commented Mar 27, 2026

Description

On ios when under pressable we had an intercepting detector handling a nested text gesture, pressable did not activate when we pressed on the part of the text with no virtual gesture attached to it. This PR should fix the issue.

Note: on web the nested text gesture never activates when under a pressable, web needs further care.

Test plan

Tested on the following example

Details
import { Text } from 'react-native';
import {
  GestureHandlerRootView,
  InterceptingGestureDetector,
  Pressable,
  VirtualGestureDetector,
  useTapGesture,
} from 'react-native-gesture-handler';

export default function Reproduction() {
  const innerTap = useTapGesture({
    onActivate: () => {
      'worklet';
      console.log('RNGH: Inner onPress');
    },
  });

  return (
    <GestureHandlerRootView
      style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Pressable
        style={{ width: 100, height: 100, backgroundColor: 'pink' }}
        onPress={() => console.log('RNGH: Pressable onPress')}>
        <InterceptingGestureDetector>
          <Text>
            Outer{' '}
            <VirtualGestureDetector gesture={innerTap}>
              <Text>Inner</Text>
            </VirtualGestureDetector>
          </Text>
        </InterceptingGestureDetector>
      </Pressable>
    </GestureHandlerRootView>
  );
}

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes iOS Pressable activation when an InterceptingGestureDetector under the pressable causes touches to land on nested text views, preventing the outer button from receiving the expected control events.

Changes:

  • Added touchesBegan/Ended/Cancelled overrides on iOS to dispatch UIControl actions when the touch originates from a subview rather than the button itself.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

[super touchesEnded:touches withEvent:event];
UITouch *touch = [touches anyObject];
if (touch.view != self) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

touchesEnded unconditionally sends UIControlEventTouchUpInside whenever the touch began on a subview (touch.view != self). This can fire the press action even when the finger is released outside the control bounds (drag-out cancellation), which breaks expected Pressable/UIControl semantics. Consider checking the touch end location against pointInside:withEvent: (respecting hitTestEdgeInsets) and sending UIControlEventTouchUpInside only when inside; otherwise send UIControlEventTouchUpOutside (or cancel as appropriate).

Suggested change
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
CGPoint location = [touch locationInView:self];
if ([self pointInside:location withEvent:event]) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
} else {
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +228 to +244
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
if (touch.view != self) {
[self sendActionsForControlEvents:UIControlEventTouchDown];
}
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
UITouch *touch = [touches anyObject];
if (touch.view != self) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
}
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

In the touch.view != self path the control manually emits only TouchDown, TouchUpInside, and TouchCancel, but never emits drag-related events (TouchDragExit/TouchDragEnter/TouchDragOutside) or TouchUpOutside. Since commonInit wires handleAnimatePressOut to TouchDragExit/TouchUpOutside, this can cause pressed visuals/state to behave differently when the finger moves outside the control before ending. Consider implementing touchesMoved: to mirror UIControl’s inside/outside tracking and emit the corresponding control events (or otherwise ensure press-out is dispatched when the touch leaves bounds).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants