1111#import < react/renderer/components/view/ViewProps.h>
1212
1313#import " RNGestureHandlerButton.h"
14+ #import " RNGestureHandlerModule.h"
1415
1516using namespace facebook ::react;
1617
@@ -36,6 +37,7 @@ @implementation RNGestureHandlerButtonComponentView {
3637 RNGestureHandlerButton *_buttonView;
3738 BOOL _needsAnimationStateReset;
3839 int _moduleId;
40+ NSNumber *_managedHandlerTag;
3941}
4042
4143#if TARGET_OS_OSX
@@ -122,6 +124,8 @@ - (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context
122124
123125- (void )prepareForRecycle
124126{
127+ [self dropManagedHandler ];
128+
125129 [self .layer removeAnimationForKey: @" transform" ];
126130 self.layer .transform = CATransform3DIdentity;
127131
@@ -254,6 +258,103 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
254258 left: borderMetrics.borderWidths.left];
255259}
256260
261+ #pragma mark - Managed gesture handler
262+
263+ - (NSDictionary *)buildManagedHandlerConfig : (const RNGestureHandlerButtonProps &)props
264+ {
265+ NSMutableDictionary *config = [NSMutableDictionary new ];
266+ config[@" shouldActivateOnStart" ] = @NO ;
267+ config[@" disallowInterruption" ] = @YES ;
268+ config[@" yieldsToContinuousGestures" ] = @YES ;
269+ config[@" enabled" ] = @(props.enabled );
270+ config[@" shouldCancelWhenOutside" ] = @(props.cancelOnLeave );
271+
272+ if (!props.gestureTestID .empty ()) {
273+ config[@" testID" ] = RCTNSStringFromString (props.gestureTestID );
274+ }
275+
276+ if (props.gestureHitSlop .top != 0 || props.gestureHitSlop .left != 0 || props.gestureHitSlop .bottom != 0 ||
277+ props.gestureHitSlop .right != 0 ) {
278+ config[@" hitSlop" ] = @{
279+ @" top" : @(props.gestureHitSlop .top ),
280+ @" left" : @(props.gestureHitSlop .left ),
281+ @" bottom" : @(props.gestureHitSlop .bottom ),
282+ @" right" : @(props.gestureHitSlop .right ),
283+ };
284+ }
285+
286+ return config;
287+ }
288+
289+ - (void )updateManagedHandler : (const RNGestureHandlerButtonProps &)newProps
290+ oldProps : (const RNGestureHandlerButtonProps *)oldProps
291+ {
292+ if (newProps.handlerTag <= 0 ) {
293+ [self dropManagedHandler ];
294+ return ;
295+ }
296+
297+ RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId: _moduleId];
298+ react_native_assert (manager != nil && " Tried to access a non-existent handler manager" );
299+
300+ BOOL tagChanged = _managedHandlerTag == nil || [_managedHandlerTag doubleValue ] != newProps.handlerTag ;
301+
302+ if (tagChanged) {
303+ [self dropManagedHandler ];
304+
305+ NSNumber *handlerTag = @(newProps.handlerTag );
306+ [manager createGestureHandler: @" NativeViewGestureHandler"
307+ tag: handlerTag
308+ config: [self buildManagedHandlerConfig: newProps]];
309+
310+ // Events dispatched by the handler carry the view's reactTag; without it the
311+ // dispatch is skipped (see `handleGesture:fromReset:fromManualStateChange:`).
312+ _buttonView.reactTag = @(self.tag );
313+ // The cast is needed on macOS, where the button is an NSControl and outside
314+ // of the RCTUIView hierarchy.
315+ [manager attachHandlerForDetectorWithTag: handlerTag
316+ toView: (RNGHUIView *)_buttonView
317+ withActionType: RNGestureHandlerActionTypeNone
318+ withHostDetector: nil ];
319+
320+ _managedHandlerTag = handlerTag;
321+ _buttonView.managedHandlerTag = handlerTag;
322+ return ;
323+ }
324+
325+ BOOL configChanged = oldProps == nullptr || oldProps->enabled != newProps.enabled ||
326+ oldProps->cancelOnLeave != newProps.cancelOnLeave || oldProps->gestureTestID != newProps.gestureTestID ||
327+ oldProps->gestureHitSlop .top != newProps.gestureHitSlop .top ||
328+ oldProps->gestureHitSlop .left != newProps.gestureHitSlop .left ||
329+ oldProps->gestureHitSlop .bottom != newProps.gestureHitSlop .bottom ||
330+ oldProps->gestureHitSlop .right != newProps.gestureHitSlop .right ;
331+
332+ if (configChanged) {
333+ // `setConfig:` resets to defaults before applying, so keys omitted from the
334+ // dictionary (e.g. `hitSlop`, `testID`) get cleared rather than kept.
335+ [manager setGestureHandlerConfig: _managedHandlerTag config: [self buildManagedHandlerConfig: newProps]];
336+ }
337+ }
338+
339+ - (void )dropManagedHandler
340+ {
341+ if (_managedHandlerTag == nil ) {
342+ return ;
343+ }
344+
345+ RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId: _moduleId];
346+ [manager dropGestureHandler: _managedHandlerTag];
347+
348+ _managedHandlerTag = nil ;
349+ _buttonView.managedHandlerTag = nil ;
350+ }
351+
352+ - (void )dealloc
353+ {
354+ // On macOS the buttons are not recycled, `prepareForRecycle` may never run.
355+ [self dropManagedHandler ];
356+ }
357+
257358#pragma mark - RNGHButtonPressEventDelegate
258359
259360- (void )dispatchButtonEvent : (RNGHButtonEventType)type withExtraData : (RNGestureHandlerEventExtraData *)extraData
@@ -475,6 +576,10 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
475576 if (shouldApplyStartAnimationState) {
476577 [_buttonView applyStartAnimationState ];
477578 }
579+
580+ const auto *oldButtonPropsPtr =
581+ treatAsFirstMount ? nullptr : std::static_pointer_cast<const RNGestureHandlerButtonProps>(oldProps).get ();
582+ [self updateManagedHandler: newProps oldProps: oldButtonPropsPtr];
478583}
479584
480585#if !TARGET_OS_OSX
0 commit comments