Skip to content

[iOS] Dragging a text selection aborts - TextBlockTapGestureRecognizer desyncs RCTSurfaceTouchHandler's touch registry #709

Description

@JaniM

Describe the bug

TextBlockTapGestureRecognizer reconfigures touch delivery on the UITextView for every touch, which desyncs React Native's RCTSurfaceTouchHandler touch registry and aborts the app when dragging a text selection.

In its initialiser (ios/utils/TextBlockTapGestureRecognizer.mm):

self.cancelsTouchesInView = YES;
self.delaysTouchesBegan = YES;
self.delaysTouchesEnded = YES;

for (UIGestureRecognizer *gr in _input->textView.gestureRecognizers) {
  [gr requireGestureRecognizerToFail:self];
}

delaysTouchesBegan = YES withholds touchesBegan: from the view hierarchy until this recognizer resolves. RCTSurfaceTouchHandler (a recognizer on the RN root view) then receives touchesMoved: for a touch it never registered in touchesBegan:, and trips its assert:

auto iterator = _activeTouches.find(touch);
RCTAssert(iterator != _activeTouches.end(), @"Inconsistency between local and UIKit touch registries");

Crash is SIGABRT, no JS error:

0 CoreFoundation    __exceptionPreprocess + 164
1 libobjc.A.dylib   objc_exception_throw + 88
3 <app>.debug.dylib -[RCTSurfaceTouchHandler _updateTouches:] + 528
4 <app>.debug.dylib -[RCTSurfaceTouchHandler touchesMoved:withEvent:] + 124
5 UIKitCore         -[UIGestureRecognizer _componentsChanged:withEve
6 UIKitCore         -[UITouchesEvent _sendEventToGestureRecognizer:] + 488
12 UIKitCore        -[UIWindow sendEvent:] + 3540
13 UIKitCore        -[UIApplication sendEvent:] + 408

The abort itself is debug-only (RCTAssert compiles out in release), but the touch registry is equally wrong in release. RN just silently skips the touch update instead of reporting it. This leads to crashes in downstream code.

Separately, the requireGestureRecognizerToFail: loop gates UITextView's own selection recognizers behind this one on every touch, which makes selection dragging occasionally buggy.

To Reproduce

  1. Run a debug build of an app rendering <EnrichedTextInput> with defaultValue containing some styled content, e.g. <html><p>some <s>struck</s>text</p><blockquote>quoted text</blockquote></html>.
  2. Long-press in the text to start a selection.
  3. Drag the selection handle across a styled region, e.g. so its end extends across the strikethrough.
  4. You might have to repeat step 3 a few times, or even restart the app to get it to trigger.
  5. App aborts with Inconsistency between local and UIKit touch registries.

We first found this reliably reproducible by dragging a selection that ends inside a blockquote, but have since hit it with strikethroughs too, so it is not specific to any one style. Plain-text drags did not reproduce the issue.

Expected behavior

Dragging a text selection should never crash, and the recognizer should not alter touch delivery.

Screenshots

N/A. The app aborts with no UI.

Device (please complete the following information):

  • Device: IPhone 16 Plus
  • OS: iOS 26.5.2 (23F84)
  • Version: react-native-enriched-html 1.0.0, react-native 0.85.3, Expo SDK 56, New Architecture

Additional context

We patched it locally by restoring stock UIKit touch delivery and dropping the loop:

self.cancelsTouchesInView = NO;
self.delaysTouchesBegan = NO;
self.delaysTouchesEnded = NO;

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions