Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
@property (nonatomic) BOOL dispatchesReanimatedEvents;
@property (nonatomic, weak, nullable) RNGHUIView *hostDetectorView;
@property (nonatomic, nullable, assign) NSNumber *virtualViewTag;
@property (nonatomic, copy, nullable) NSNumber *viewTag;

- (BOOL)isViewParagraphComponent:(nullable RNGHUIView *)view;
- (nonnull RNGHUIView *)chooseViewForInteraction:(nonnull UIGestureRecognizer *)recognizer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ - (void)bindToView:(RNGHUIView *)view

[recognizerView addGestureRecognizer:self.recognizer];
[self bindManualActivationToView:recognizerView];

self.viewTag = view.reactTag;
}

- (void)unbindFromView
Expand All @@ -271,6 +273,7 @@ - (void)unbindFromView

self.hostDetectorView = nil;
self.virtualViewTag = nil;
self.viewTag = nil;

[self unbindManualActivation];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@
- (nullable RNGestureHandler *)handlerWithTag:(nonnull NSNumber *)handlerTag;

- (nullable RNGHUIView *)viewForReactTag:(nonnull NSNumber *)reactTag;

- (void)reattachHandlersIfNeeded;
@end
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,21 @@ - (void)attachGestureHandler:(nonnull NSNumber *)handlerTag
}

[_attachRetryCounter removeObjectForKey:viewTag];
[self maybeBindHandler:handlerTag toViewWithTag:viewTag withActionType:actionType withHostDetector:hostDetector];
}

// Resolves a view tag to its native UIView (including contentView unwrapping),
// sets reactTag, and binds the gesture handler to it. No-op if the handler is
// already attached to the correct view.
- (void)maybeBindHandler:(nonnull NSNumber *)handlerTag
toViewWithTag:(nonnull NSNumber *)viewTag
withActionType:(RNGestureHandlerActionType)actionType
withHostDetector:(nullable RNGHUIView *)hostDetector
{
RNGHUIView *view = [_viewRegistry viewForReactTag:viewTag];
if (view == nil || view.superview == nil) {
return;
}

// I think it should be moved to RNNativeViewHandler, but that would require
// additional logic for setting contentView.reactTag, this works for now
Expand All @@ -181,12 +196,17 @@ - (void)attachGestureHandler:(nonnull NSNumber *)handlerTag
}
}

RNGestureHandler *handler = [_registry handlerWithTag:handlerTag];

// Already attached to the correct native view, nothing to do.
if (handler != nil && handler.recognizer.view == view && handler.actionType == actionType) {
return;
}

view.reactTag = viewTag; // necessary for RNReanimated eventHash (e.g. "42onGestureHandlerEvent"), also will be
// returned as event.target

[_registry attachHandlerWithTag:handlerTag toView:view withActionType:actionType withHostDetector:hostDetector];

// register view if not already there
[self registerViewWithGestureRecognizerAttachedIfNeeded:view];
}

Expand Down Expand Up @@ -224,6 +244,24 @@ - (id)handlerWithTag:(NSNumber *)handlerTag
return [_registry handlerWithTag:handlerTag];
}

- (void)reattachHandlersIfNeeded
{
// Re-bind handlers to their current native views. On Fabric, when a parent view has
// display:none and siblings change, the native UIView backing a component may be recycled
// and replaced. maybeBindHandler is a no-op if the view is nil or unchanged. This is only
// needed for handlers using the old api.
for (RNGestureHandler *handler in _registry.handlers.objectEnumerator) {
if (handler.viewTag == nil || [handler usesNativeOrVirtualDetector]) {
continue;
}

[self maybeBindHandler:handler.tag
toViewWithTag:handler.viewTag
withActionType:handler.actionType
withHostDetector:nil];
}
}
Comment on lines +247 to +263
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

This enumerates _registry.handlers while calling maybeBindHandler, which in turn calls into registry attach logic (attachHandlerWithTag:...). If the registry mutates its handlers dictionary as part of (re)attach, this risks a “mutated while being enumerated” crash. Safer approach: iterate over a snapshot (e.g., _registry.handlers.allValues captured into an array before the loop, or iterate over a copied dictionary’s values).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is safe because attachHandlerWithTag:toView:withActionType:withHostDetector: in the registry only mutates existing entries (calls unbindFromView/bindToView on a handler that's already in the dictionary). It never adds or removes keys from _handlers, so the dictionary is not structurally mutated during enumeration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using objectEnumerator here instead of allValues to avoid allocating a temporary array on every flush.


#pragma mark Root Views Management

- (void)registerViewWithGestureRecognizerAttachedIfNeeded:(RNGHUIView *)childView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,21 @@ - (void)flushOperations
{
// On the new arch we rely on `flushOperations` for scheduling the operations on the UI thread.
// On the old arch we rely on `uiManagerWillPerformMounting`
if (_operations.count == 0) {
return;
}

RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId];
NSArray<GestureHandlerOperation> *operations = _operations;
_operations = [NSMutableArray new];

if (_operations.count > 0) {
NSArray<GestureHandlerOperation> *operations = _operations;
_operations = [NSMutableArray new];

[self.viewRegistry_DEPRECATED addUIBlock:^(RCTViewRegistry *viewRegistry) {
for (GestureHandlerOperation operation in operations) {
operation(manager);
}
}];
}

[self.viewRegistry_DEPRECATED addUIBlock:^(RCTViewRegistry *viewRegistry) {
for (GestureHandlerOperation operation in operations) {
operation(manager);
}
[manager reattachHandlersIfNeeded];
}];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@
- (void)dropHandlerWithTag:(nonnull NSNumber *)handlerTag;
- (void)dropAllHandlers;

@property (nonatomic, readonly, nonnull) NSDictionary<NSNumber *, RNGestureHandler *> *handlers;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ - (instancetype)init
return self;
}

- (NSDictionary<NSNumber *, RNGestureHandler *> *)handlers
{
return _handlers;
}

- (RNGestureHandler *)handlerWithTag:(NSNumber *)handlerTag
{
return _handlers[handlerTag];
Expand Down
Loading