Skip to content

Commit 0f6398a

Browse files
committed
Allow bound views to listen to handler updates and state changes
1 parent ad283d6 commit 0f6398a

4 files changed

Lines changed: 103 additions & 16 deletions

File tree

packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,25 @@
1111
@interface RNDummyGestureRecognizer : UIGestureRecognizer
1212
@end
1313

14+
/*
15+
* Views bound to a `RNNativeViewGestureHandler` may conform to this protocol to
16+
* be notified about the handler's updates and state changes.
17+
*/
18+
@protocol RNGHNativeViewHandlerStateObserver <NSObject>
19+
20+
/*
21+
* Called when the handler dispatches a new update event in the ACTIVE state.
22+
*/
23+
- (void)onHandlerUpdate:(nonnull RNGestureHandlerEventExtraData *)extraData;
24+
25+
/*
26+
* Called when the handler moves to a new state.
27+
*/
28+
- (void)onHandlerStateChange:(RNGestureHandlerState)newState
29+
prevState:(RNGestureHandlerState)prevState
30+
extraData:(nonnull RNGestureHandlerEventExtraData *)extraData;
31+
32+
@end
33+
1434
@interface RNNativeViewGestureHandler : RNGestureHandler
1535
@end

packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ @implementation RNNativeViewGestureHandler {
116116
BOOL _yieldsToContinuousGestures;
117117
BOOL _delaysChildPressedState;
118118
RNGestureHandlerEventExtraData *_lastActiveExtraData;
119+
120+
// For UIControl-based views the recognizer is never attached, so
121+
// `recognizer.view` cannot be used to retrieve the bound view.
122+
__weak RNGHUIView *_boundView;
123+
__weak id<RNGHNativeViewHandlerStateObserver> _stateObserver;
119124
}
120125

121126
- (instancetype)initWithTag:(NSNumber *)tag
@@ -146,9 +151,54 @@ - (void)updateConfig:(NSDictionary *)config
146151
#endif
147152
}
148153

154+
- (void)bindToView:(RNGHUIView *)view
155+
{
156+
_boundView = view;
157+
158+
if ([view conformsToProtocol:@protocol(RNGHNativeViewHandlerStateObserver)]) {
159+
_stateObserver = (id<RNGHNativeViewHandlerStateObserver>)view;
160+
}
161+
162+
#if !TARGET_OS_OSX
163+
[self bindToUIKitView:view];
164+
#else
165+
[super bindToView:view];
166+
#endif
167+
}
168+
169+
- (void)unbindFromView
170+
{
171+
#if !TARGET_OS_OSX
172+
if ([_boundView isKindOfClass:[UIControl class]]) {
173+
[(UIControl *)_boundView removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents];
174+
}
175+
176+
// Restore the React Native's overriden behavor for not delaying content touches
177+
UIScrollView *scrollView = [self retrieveScrollView:_boundView];
178+
scrollView.delaysContentTouches = NO;
179+
#endif
180+
181+
_boundView = nil;
182+
_stateObserver = nil;
183+
184+
[super unbindFromView];
185+
}
186+
187+
- (void)dispatchStateChange:(RNGestureHandlerState)newState
188+
prevState:(RNGestureHandlerState)prevState
189+
extraData:(RNGestureHandlerEventExtraData *)extraData
190+
{
191+
[_stateObserver onHandlerStateChange:newState prevState:prevState extraData:extraData];
192+
}
193+
194+
- (void)dispatchHandlerUpdate:(RNGestureHandlerEventExtraData *)extraData
195+
{
196+
[_stateObserver onHandlerUpdate:extraData];
197+
}
198+
149199
#if !TARGET_OS_OSX
150200

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

199-
- (void)unbindFromView
200-
{
201-
UIView *view = self.recognizer.view;
202-
203-
if ([view isKindOfClass:[UIControl class]]) {
204-
[(UIControl *)view removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents];
205-
}
206-
207-
// Restore the React Native's overriden behavor for not delaying content touches
208-
UIScrollView *scrollView = [self retrieveScrollView:view];
209-
scrollView.delaysContentTouches = NO;
210-
211-
[super unbindFromView];
212-
}
213-
214249
- (RNGestureHandlerEventExtraData *)extraDataForView:(UIView *)sender
215250
event:(UIEvent *)event
216251
pointerInside:(BOOL)pointerInside

packages/react-native-gesture-handler/apple/RNGestureHandler.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,21 @@
125125

126126
- (void)stopActivationBlocker;
127127
- (void)reset;
128+
129+
/*
130+
* Called after a state-change event has been dispatched. No-op by default, may be
131+
* overridden by subclasses to observe the state flow regardless of the action type.
132+
*/
133+
- (void)dispatchStateChange:(RNGestureHandlerState)newState
134+
prevState:(RNGestureHandlerState)prevState
135+
extraData:(nonnull RNGestureHandlerEventExtraData *)extraData;
136+
137+
/*
138+
* Called after an update event has been dispatched in the ACTIVE state. No-op by
139+
* default, may be overridden by subclasses.
140+
*/
141+
- (void)dispatchHandlerUpdate:(nonnull RNGestureHandlerEventExtraData *)extraData;
142+
128143
- (void)sendEventsInState:(RNGestureHandlerState)state
129144
forViewWithTag:(nonnull NSNumber *)reactTag
130145
withExtraData:(nonnull RNGestureHandlerEventExtraData *)extraData;

packages/react-native-gesture-handler/apple/RNGestureHandler.mm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,21 +534,25 @@ - (void)sendEventsInState:(RNGestureHandlerState)state
534534
state == RNGestureHandlerStateEnd && _lastState != RNGestureHandlerStateActive && !fromManualStateChange &&
535535
!_manualActivation) {
536536
// Otherwise send activate state change event to preserve correct gesture flow
537+
RNGestureHandlerState prevSynthesizedState = _lastState;
537538
id event = [[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
538539
handlerTag:_tag
539540
state:RNGestureHandlerStateActive
540541
prevState:_lastState
541542
extraData:extraData];
542543
[self sendEvent:event];
543544
_lastState = RNGestureHandlerStateActive;
545+
[self dispatchStateChange:RNGestureHandlerStateActive prevState:prevSynthesizedState extraData:extraData];
544546
}
547+
RNGestureHandlerState prevState = _lastState;
545548
id stateEvent = [[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
546549
handlerTag:_tag
547550
state:state
548551
prevState:_lastState
549552
extraData:extraData];
550553
[self sendEvent:stateEvent];
551554
_lastState = state;
555+
[self dispatchStateChange:state prevState:prevState extraData:extraData];
552556
}
553557

554558
if (state == RNGestureHandlerStateActive) {
@@ -559,9 +563,22 @@ - (void)sendEventsInState:(RNGestureHandlerState)state
559563
forHandlerType:[self eventHandlerType]
560564
coalescingKey:self->_eventCoalescingKey];
561565
[self sendEvent:touchEvent];
566+
[self dispatchHandlerUpdate:extraData];
562567
}
563568
}
564569

570+
- (void)dispatchStateChange:(RNGestureHandlerState)newState
571+
prevState:(RNGestureHandlerState)prevState
572+
extraData:(RNGestureHandlerEventExtraData *)extraData
573+
{
574+
// no-op
575+
}
576+
577+
- (void)dispatchHandlerUpdate:(RNGestureHandlerEventExtraData *)extraData
578+
{
579+
// no-op
580+
}
581+
565582
- (RNGHUIView *)findViewForEvents
566583
{
567584
return [self usesNativeOrVirtualDetector] ? self.hostDetectorView : self.recognizer.view;

0 commit comments

Comments
 (0)