Skip to content
Open
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 @@ -152,6 +152,11 @@ class RNGestureHandlerButtonViewManager :
view.hasLongPressHandler = hasLongPressHandler
}

// No-op — only iOS needs the module id to resolve the handler manager, Android
// gets the module from the view's context.
@ReactProp(name = "moduleId")
override fun setModuleId(view: ButtonViewGroup, moduleId: Int) = Unit

@ReactProp(name = "foreground")
override fun setForeground(view: ButtonViewGroup, useDrawableOnForeground: Boolean) {
view.useDrawableOnForeground = useDrawableOnForeground
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ class RNGestureHandlerButtonEvent private constructor() : Event<RNGestureHandler
}

companion object {
private const val ON_PRESS_EVENT_NAME = "onPress"
private const val ON_PRESS_IN_EVENT_NAME = "onPressIn"
private const val ON_PRESS_OUT_EVENT_NAME = "onPressOut"
private const val ON_LONG_PRESS_EVENT_NAME = "onLongPress"
private const val ON_INTERACTION_FINISHED_EVENT_NAME = "onInteractionFinished"
private const val ON_PRESS_EVENT_NAME = "onButtonPress"
private const val ON_PRESS_IN_EVENT_NAME = "onButtonPressIn"
private const val ON_PRESS_OUT_EVENT_NAME = "onButtonPressOut"
private const val ON_LONG_PRESS_EVENT_NAME = "onButtonLongPress"
private const val ON_INTERACTION_FINISHED_EVENT_NAME = "onButtonInteractionFinished"

private const val TOUCH_EVENTS_POOL_SIZE = 7 // magic
private val EVENTS_POOL = Pools.SynchronizedPool<RNGestureHandlerButtonEvent>(TOUCH_EVENTS_POOL_SIZE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,25 @@
@interface RNDummyGestureRecognizer : UIGestureRecognizer
@end

/*
* Views bound to a `RNNativeViewGestureHandler` may conform to this protocol to
* be notified about the handler's updates and state changes.
*/
@protocol RNGHNativeViewHandlerStateObserver <NSObject>

/*
* Called when the handler dispatches a new update event in the ACTIVE state.
*/
- (void)onHandlerUpdate:(nonnull RNGestureHandlerEventExtraData *)extraData;

/*
* Called when the handler moves to a new state.
*/
- (void)onHandlerStateChange:(RNGestureHandlerState)newState
prevState:(RNGestureHandlerState)prevState
extraData:(nonnull RNGestureHandlerEventExtraData *)extraData;

@end

@interface RNNativeViewGestureHandler : RNGestureHandler
@end
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ @implementation RNNativeViewGestureHandler {
BOOL _yieldsToContinuousGestures;
BOOL _delaysChildPressedState;
RNGestureHandlerEventExtraData *_lastActiveExtraData;

// For UIControl-based views the recognizer is never attached, so
// `recognizer.view` cannot be used to retrieve the bound view.
__weak RNGHUIView *_boundView;
__weak id<RNGHNativeViewHandlerStateObserver> _stateObserver;
}

- (instancetype)initWithTag:(NSNumber *)tag
Expand Down Expand Up @@ -146,9 +151,54 @@ - (void)updateConfig:(NSDictionary *)config
#endif
}

- (void)bindToView:(RNGHUIView *)view
{
_boundView = view;

if ([view conformsToProtocol:@protocol(RNGHNativeViewHandlerStateObserver)]) {
_stateObserver = (id<RNGHNativeViewHandlerStateObserver>)view;
}

#if !TARGET_OS_OSX
[self bindToUIKitView:view];
#else
[super bindToView:view];
#endif
}

- (void)unbindFromView
{
#if !TARGET_OS_OSX
if ([_boundView isKindOfClass:[UIControl class]]) {
[(UIControl *)_boundView removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents];
}

// Restore the React Native's overridden behavor for not delaying content touches
UIScrollView *scrollView = [self retrieveScrollView:_boundView];
scrollView.delaysContentTouches = NO;
#endif

_boundView = nil;
_stateObserver = nil;

[super unbindFromView];
}

- (void)dispatchStateChange:(RNGestureHandlerState)newState
prevState:(RNGestureHandlerState)prevState
extraData:(RNGestureHandlerEventExtraData *)extraData
{
[_stateObserver onHandlerStateChange:newState prevState:prevState extraData:extraData];
}

- (void)dispatchHandlerUpdate:(RNGestureHandlerEventExtraData *)extraData
{
[_stateObserver onHandlerUpdate:extraData];
}

#if !TARGET_OS_OSX

- (void)bindToView:(UIView *)view
- (void)bindToUIKitView:(UIView *)view
{
// For UIControl based views (UIButton, UISwitch) we provide special handling that would allow
// for properties like `disallowInterruption` to work.
Expand Down Expand Up @@ -196,21 +246,6 @@ - (void)bindToView:(UIView *)view
scrollView.delaysContentTouches = _delaysChildPressedState;
}

- (void)unbindFromView
{
UIView *view = self.recognizer.view;

if ([view isKindOfClass:[UIControl class]]) {
[(UIControl *)view removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents];
}

// Restore the React Native's overriden behavor for not delaying content touches
UIScrollView *scrollView = [self retrieveScrollView:view];
scrollView.delaysContentTouches = NO;

[super unbindFromView];
}

- (RNGestureHandlerEventExtraData *)extraDataForView:(UIView *)sender
event:(UIEvent *)event
pointerInside:(BOOL)pointerInside
Expand Down
15 changes: 15 additions & 0 deletions packages/react-native-gesture-handler/apple/RNGestureHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@

- (void)stopActivationBlocker;
- (void)reset;

/*
* Called after a state-change event has been dispatched. No-op by default, may be
* overridden by subclasses to observe the state flow regardless of the action type.
*/
- (void)dispatchStateChange:(RNGestureHandlerState)newState
prevState:(RNGestureHandlerState)prevState
extraData:(nonnull RNGestureHandlerEventExtraData *)extraData;

/*
* Called after an update event has been dispatched in the ACTIVE state. No-op by
* default, may be overridden by subclasses.
*/
- (void)dispatchHandlerUpdate:(nonnull RNGestureHandlerEventExtraData *)extraData;

- (void)sendEventsInState:(RNGestureHandlerState)state
forViewWithTag:(nonnull NSNumber *)reactTag
withExtraData:(nonnull RNGestureHandlerEventExtraData *)extraData;
Expand Down
17 changes: 17 additions & 0 deletions packages/react-native-gesture-handler/apple/RNGestureHandler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -534,21 +534,25 @@ - (void)sendEventsInState:(RNGestureHandlerState)state
state == RNGestureHandlerStateEnd && _lastState != RNGestureHandlerStateActive && !fromManualStateChange &&
!_manualActivation) {
// Otherwise send activate state change event to preserve correct gesture flow
RNGestureHandlerState prevSynthesizedState = _lastState;
id event = [[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
handlerTag:_tag
state:RNGestureHandlerStateActive
prevState:_lastState
extraData:extraData];
[self sendEvent:event];
_lastState = RNGestureHandlerStateActive;
[self dispatchStateChange:RNGestureHandlerStateActive prevState:prevSynthesizedState extraData:extraData];
}
RNGestureHandlerState prevState = _lastState;
id stateEvent = [[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
handlerTag:_tag
state:state
prevState:_lastState
extraData:extraData];
[self sendEvent:stateEvent];
_lastState = state;
[self dispatchStateChange:state prevState:prevState extraData:extraData];
}

if (state == RNGestureHandlerStateActive) {
Expand All @@ -559,9 +563,22 @@ - (void)sendEventsInState:(RNGestureHandlerState)state
forHandlerType:[self eventHandlerType]
coalescingKey:self->_eventCoalescingKey];
[self sendEvent:touchEvent];
[self dispatchHandlerUpdate:extraData];
}
}

- (void)dispatchStateChange:(RNGestureHandlerState)newState
prevState:(RNGestureHandlerState)prevState
extraData:(RNGestureHandlerEventExtraData *)extraData
{
// no-op
}

- (void)dispatchHandlerUpdate:(RNGestureHandlerEventExtraData *)extraData
{
// no-op
}

- (RNGHUIView *)findViewForEvents
{
return [self usesNativeOrVirtualDetector] ? self.hostDetectorView : self.recognizer.view;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, RNGestureHandlerActionType) {
RNGestureHandlerActionTypeNone = 0, // Handler managed by a native component, doesn't dispatch events to JS
RNGestureHandlerActionTypeReanimatedWorklet = 1, // Reanimated worklet
RNGestureHandlerActionTypeNativeAnimatedEvent, // Animated.event with useNativeDriver: true
RNGestureHandlerActionTypeJSFunctionOldAPI, // JS function or Animated.event with useNativeDriver: false using old
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@

#import "RNGestureHandler.h"

typedef NS_ENUM(NSInteger, RNGHButtonEventType) {
RNGHButtonEventTypePress,
RNGHButtonEventTypePressIn,
RNGHButtonEventTypePressOut,
RNGHButtonEventTypeLongPress,
RNGHButtonEventTypeInteractionFinished,
};

/*
* Receives press events produced by the button's state machine so that they can
* be dispatched through the component's event emitter.
*/
@protocol RNGHButtonPressEventDelegate <NSObject>

- (void)dispatchButtonEvent:(RNGHButtonEventType)type
withExtraData:(nullable RNGestureHandlerEventExtraData *)extraData;

@end

#if TARGET_OS_OSX

#include <react/renderer/core/LayoutMetrics.h>
Expand Down Expand Up @@ -43,6 +62,15 @@
@property (nonatomic, assign) CGFloat hoverScale;
@property (nonatomic, assign) CGFloat hoverUnderlayOpacity;
@property (nonatomic, strong, nullable) RNGHColor *underlayColor;
@property (nonatomic, assign) BOOL hasLongPressHandler;

/**
* Tag of the gesture handler managed by the button component. The press event
* state machine runs only while it's set.
*/
@property (nonatomic, strong, nullable) NSNumber *managedHandlerTag;

@property (nonatomic, weak, nullable) id<RNGHButtonPressEventDelegate> pressEventDelegate;

/**
* The view that press animations are applied to. Defaults to self; set by the
Expand Down
Loading
Loading