From 431fd2211decf6809097bcdc0eb602e2a794ee19 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Thu, 20 Jun 2024 11:50:20 +0200 Subject: [PATCH] fix(iOS, Fabric): add missing logic for finding touch handler (#2193) ## Description `- [RNSScreen touchHandler]` method was missing implementation for Fabric, adding it here. ## Changes - **Add category on UIView with code for touch handler finding** - **Use the new category to simplify implementation** ## Test code and steps to reproduce `Test2118` - tested on both archs & works just fine ## Checklist - [x] Included code example that can be used to test this change - [x] Ensured that CI passes --- ios/RNSScreen.mm | 17 ++++------- ios/RNSScreenStack.mm | 39 ++---------------------- ios/utils/UIView+RNSUtility.h | 23 ++++++++++++++ ios/utils/UIView+RNSUtility.mm | 55 ++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 48 deletions(-) create mode 100644 ios/utils/UIView+RNSUtility.h create mode 100644 ios/utils/UIView+RNSUtility.mm diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index ff804e0772..2da6d9ad7e 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -25,6 +25,8 @@ #import "RNSScreenStack.h" #import "RNSScreenStackHeaderConfig.h" +#import "UIView+RNSUtility.h" + #ifdef RCT_NEW_ARCH_ENABLED namespace react = facebook::react; #endif // RCT_NEW_ARCH_ENABLED @@ -473,22 +475,13 @@ - (void)didMoveToWindow } } -#ifdef RCT_NEW_ARCH_ENABLED -- (RCTSurfaceTouchHandler *)touchHandler -#else -- (RCTTouchHandler *)touchHandler -#endif +- (nullable RNS_TOUCH_HANDLER_ARCH_TYPE *)touchHandler { if (_touchHandler != nil) { return _touchHandler; } - UIView *parent = [self superview]; - while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)]) - parent = parent.superview; - if (parent != nil) { - return [parent performSelector:@selector(touchHandler)]; - } - return nil; + + return [self rnscreens_findTouchHandlerInAncestorChain]; } - (void)notifyFinishTransitioning diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index c84924b6d0..5e092566da 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -26,6 +26,8 @@ #import "RNSScreenStackHeaderConfig.h" #import "RNSScreenWindowTraits.h" +#import "UIView+RNSUtility.h" + #ifdef RCT_NEW_ARCH_ENABLED namespace react = facebook::react; #endif // RCT_NEW_ARCH_ENABLED @@ -729,43 +731,8 @@ - (void)cancelTouchesInParent // item is close to an edge and we start pulling from edge we want the Touchable to be cancelled. // Without the below code the Touchable will remain active (highlighted) for the duration of back // gesture and onPress may fire when we release the finger. -#ifdef RCT_NEW_ARCH_ENABLED - // On Fabric there is no view that exposes touchHandler above us in the view hierarchy, however it is still - // utilised. `RCTSurfaceView` should be present above us, which hosts `RCTFabricSurface` instance, which in turn - // hosts `RCTSurfaceTouchHandler` as a private field. When initialised, `RCTSurfaceTouchHandler` is attached to the - // surface view as a gestureRecognizer <- and this is where we can lay our hands on it. - UIView *parent = _controller.view; - while (parent != nil && ![parent isKindOfClass:RCTSurfaceView.class]) { - parent = parent.superview; - } - // This could be possible in modal context - if (parent == nil) { - return; - } - - RCTSurfaceTouchHandler *touchHandler = nil; - // Experimentation shows that RCTSurfaceTouchHandler is the only gestureRecognizer registered here, - // so we should not be afraid of any performance hit here. - for (UIGestureRecognizer *recognizer in parent.gestureRecognizers) { - if ([recognizer isKindOfClass:RCTSurfaceTouchHandler.class]) { - touchHandler = static_cast(recognizer); - } - } - - [touchHandler rnscreens_cancelTouches]; -#else - // On Paper we can access touchHandler hosted by `RCTRootContentView` which should be above ScreenStack - // in view hierarchy. - UIView *parent = _controller.view; - while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)]) { - parent = parent.superview; - } - if (parent != nil) { - RCTTouchHandler *touchHandler = [parent performSelector:@selector(touchHandler)]; - [touchHandler rnscreens_cancelTouches]; - } -#endif // RCT_NEW_ARCH_ENABLED + [[self rnscreens_findTouchHandlerInAncestorChain] rnscreens_cancelTouches]; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer diff --git a/ios/utils/UIView+RNSUtility.h b/ios/utils/UIView+RNSUtility.h new file mode 100644 index 0000000000..90cf236066 --- /dev/null +++ b/ios/utils/UIView+RNSUtility.h @@ -0,0 +1,23 @@ +#import + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#else +#import +#endif // RCT_NEW_ARCH_ENABLED + +#ifdef RCT_NEW_ARCH_ENABLED +#define RNS_TOUCH_HANDLER_ARCH_TYPE RCTSurfaceTouchHandler +#else +#define RNS_TOUCH_HANDLER_ARCH_TYPE RCTTouchHandler +#endif // RCT_NEW_ARCH_ENABLED + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (RNSUtility) + +- (nullable RNS_TOUCH_HANDLER_ARCH_TYPE *)rnscreens_findTouchHandlerInAncestorChain; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/utils/UIView+RNSUtility.mm b/ios/utils/UIView+RNSUtility.mm new file mode 100644 index 0000000000..6889b7445e --- /dev/null +++ b/ios/utils/UIView+RNSUtility.mm @@ -0,0 +1,55 @@ + +#import "UIView+RNSUtility.h" + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#endif + +@implementation UIView (RNSUtility) + +- (nullable RNS_TOUCH_HANDLER_ARCH_TYPE *)rnscreens_findTouchHandlerInAncestorChain +{ + UIView *parent = self; + +#ifdef RCT_NEW_ARCH_ENABLED + // On Fabric there is no view that exposes touchHandler above us in the view hierarchy, however it is still + // utilised. `RCTSurfaceView` should be present above us, which hosts `RCTFabricSurface` instance, which in turn + // hosts `RCTSurfaceTouchHandler` as a private field. When initialised, `RCTSurfaceTouchHandler` is attached to the + // surface view as a gestureRecognizer <- and this is where we can lay our hands on it. + + while (parent != nil && ![parent isKindOfClass:RCTSurfaceView.class]) { + parent = parent.superview; + } + + // This could be possible in modal context + if (parent == nil) { + return nil; + } + + // Experimentation shows that RCTSurfaceTouchHandler is the only gestureRecognizer registered here, + // so we should not be afraid of any performance hit here. + for (UIGestureRecognizer *recognizer in parent.gestureRecognizers) { + if ([recognizer isKindOfClass:RCTSurfaceTouchHandler.class]) { + return static_cast(recognizer); + } + } + +#else + + // On Paper we can access touchHandler hosted by `RCTRootContentView` which should be above ScreenStack + // in view hierarchy. + while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)]) { + parent = parent.superview; + } + + if (parent != nil) { + RCTTouchHandler *touchHandler = [parent performSelector:@selector(touchHandler)]; + return static_cast(touchHandler); + } + +#endif // RCT_NEW_ARCH_ENABLED + + return nil; +} + +@end