Skip to content

Commit 9926b7c

Browse files
j-piaseckitomekzaw
andauthored
Fix operation scheduling on iOS (#2483)
## Description #2467 replaces calls to `setImmediate` and `requestAnimationFrame` with `queueMicrotask`. This introduces a bug, where on iOS the `flushOperations` method would get called too early. This PR changes `flushOperations` to only be used on the new architecture, where it's actually required, and adds a retry mechanism for attaching handlers to native views. It also updates parts of the new arch-related code to function properly - finding the recognizer responsible for the touch responder and attaching the root view handler. Fixes #2482 ## Test plan Test on the Example and FabricExample apps. --------- Co-authored-by: Tomek Zawadzki <tomekzawadzki98@gmail.com>
1 parent 5c25dc2 commit 9926b7c

File tree

2 files changed

+60
-7
lines changed

2 files changed

+60
-7
lines changed

ios/RNGestureHandlerManager.mm

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#ifdef RCT_NEW_ARCH_ENABLED
1818
#import <React/RCTSurfaceTouchHandler.h>
19+
#import <React/RCTSurfaceView.h>
1920
#import <React/RCTViewComponentView.h>
2021
#else
2122
#import <React/RCTTouchHandler.h>
@@ -37,6 +38,8 @@
3738
RCTDefaultLogFunction( \
3839
RCTLogLevelInfo, RCTLogSourceNative, @(__FILE__), @(__LINE__), [NSString stringWithFormat:__VA_ARGS__])
3940

41+
constexpr int NEW_ARCH_NUMBER_OF_ATTACH_RETRIES = 25;
42+
4043
@interface RNGestureHandlerManager () <RNGestureHandlerEventEmitter, RNRootViewGestureRecognizerDelegate>
4144

4245
@end
@@ -45,6 +48,7 @@ @implementation RNGestureHandlerManager {
4548
RNGestureHandlerRegistry *_registry;
4649
RCTUIManager *_uiManager;
4750
NSHashTable<RNRootViewGestureRecognizer *> *_rootViewGestureRecognizers;
51+
NSMutableDictionary<NSNumber *, NSNumber *> *_attachRetryCounter;
4852
RCTEventDispatcher *_eventDispatcher;
4953
id _reanimatedModule;
5054
}
@@ -56,6 +60,7 @@ - (instancetype)initWithUIManager:(RCTUIManager *)uiManager eventDispatcher:(RCT
5660
_eventDispatcher = eventDispatcher;
5761
_registry = [RNGestureHandlerRegistry new];
5862
_rootViewGestureRecognizers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
63+
_attachRetryCounter = [[NSMutableDictionary alloc] init];
5964
_reanimatedModule = nil;
6065
}
6166
return self;
@@ -100,12 +105,39 @@ - (void)attachGestureHandler:(nonnull NSNumber *)handlerTag
100105
UIView *view = [_uiManager viewForReactTag:viewTag];
101106

102107
#ifdef RCT_NEW_ARCH_ENABLED
103-
if (view == nil) {
104-
// Happens when the view with given tag has been flattened.
105-
// We cannot attach gesture handler to a non-existent view.
108+
if (view == nil || view.superview == nil) {
109+
// There are a few reasons we could end up here:
110+
// - the native view corresponding to the viewtag hasn't yet been created
111+
// - the native view has been created, but it's not attached to window
112+
// - the native view will not exist because it got flattened
113+
// In the first two cases we just want to wait until the view gets created or gets attached to its superview
114+
// In the third case we don't want to do anything but we cannot easily distinguish it here, hece the abomination
115+
// below
116+
// TODO: would be great to have a better solution, although it might require migration to the shadow nodes from
117+
// viewTags
118+
119+
NSNumber *counter = [_attachRetryCounter objectForKey:viewTag];
120+
if (counter == nil) {
121+
counter = @1;
122+
} else {
123+
counter = [NSNumber numberWithInt:counter.intValue + 1];
124+
}
125+
126+
if (counter.intValue > NEW_ARCH_NUMBER_OF_ATTACH_RETRIES) {
127+
[_attachRetryCounter removeObjectForKey:viewTag];
128+
} else {
129+
[_attachRetryCounter setObject:counter forKey:viewTag];
130+
131+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
132+
[self attachGestureHandler:handlerTag toViewWithTag:viewTag withActionType:actionType];
133+
});
134+
}
135+
106136
return;
107137
}
108138

139+
[_attachRetryCounter removeObjectForKey:viewTag];
140+
109141
// I think it should be moved to RNNativeViewHandler, but that would require
110142
// additional logic for setting contentView.reactTag, this works for now
111143
if ([view isKindOfClass:[RCTViewComponentView class]]) {
@@ -164,18 +196,26 @@ - (id)handlerWithTag:(NSNumber *)handlerTag
164196

165197
- (void)registerViewWithGestureRecognizerAttachedIfNeeded:(UIView *)childView
166198
{
199+
#ifdef RCT_NEW_ARCH_ENABLED
200+
UIView *touchHandlerView = childView;
201+
202+
while (touchHandlerView != nil && ![touchHandlerView isKindOfClass:[RCTSurfaceView class]]) {
203+
touchHandlerView = touchHandlerView.superview;
204+
}
205+
#else
167206
UIView *parent = childView;
168207
while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)])
169208
parent = parent.superview;
170209

171-
// Many views can return the same touchHandler so we check if the one we want to register
172-
// is not already present in the set.
173210
UIView *touchHandlerView = [[parent performSelector:@selector(touchHandler)] view];
211+
#endif // RCT_NEW_ARCH_ENABLED
174212

175213
if (touchHandlerView == nil) {
176214
return;
177215
}
178216

217+
// Many views can return the same touchHandler so we check if the one we want to register
218+
// is not already present in the set.
179219
for (UIGestureRecognizer *recognizer in touchHandlerView.gestureRecognizers) {
180220
if ([recognizer isKindOfClass:[RNRootViewGestureRecognizer class]]) {
181221
return;
@@ -208,10 +248,19 @@ - (void)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
208248
return;
209249

210250
#ifdef RCT_NEW_ARCH_ENABLED
211-
RCTSurfaceTouchHandler *touchHandler = [viewWithTouchHandler performSelector:@selector(touchHandler)];
251+
UIGestureRecognizer *touchHandler = nil;
252+
253+
// touchHandler (RCTSurfaceTouchHandler) is private in RCTFabricSurface so we have to do
254+
// this little trick to get access to it
255+
for (UIGestureRecognizer *recognizer in [viewWithTouchHandler gestureRecognizers]) {
256+
if ([recognizer isKindOfClass:[RCTSurfaceTouchHandler class]]) {
257+
touchHandler = recognizer;
258+
break;
259+
}
260+
}
212261
#else
213262
RCTTouchHandler *touchHandler = [viewWithTouchHandler performSelector:@selector(touchHandler)];
214-
#endif
263+
#endif // RCT_NEW_ARCH_ENABLED
215264
[touchHandler setEnabled:NO];
216265
[touchHandler setEnabled:YES];
217266
}

ios/RNGestureHandlerModule.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ - (void)setBridge:(RCTBridge *)bridge
184184

185185
RCT_EXPORT_METHOD(flushOperations)
186186
{
187+
// On the new arch we rely on `flushOperations` for scheduling the operations on the UI thread.
188+
// On the old arch we rely on `uiManagerWillPerformMounting`
189+
#ifdef RCT_NEW_ARCH_ENABLED
187190
if (_operations.count == 0) {
188191
return;
189192
}
@@ -197,6 +200,7 @@ - (void)setBridge:(RCTBridge *)bridge
197200
operation(self->_manager);
198201
}
199202
}];
203+
#endif // RCT_NEW_ARCH_ENABLED
200204
}
201205

202206
- (void)setGestureState:(int)state forHandler:(int)handlerTag

0 commit comments

Comments
 (0)