Skip to content

Commit

Permalink
fix(iOS, Fabric): add missing logic for finding touch handler (#2193)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
kkafar authored and alduzy committed Jun 28, 2024
1 parent c1373d6 commit 431fd22
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 48 deletions.
17 changes: 5 additions & 12 deletions ios/RNSScreen.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
39 changes: 3 additions & 36 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<RCTSurfaceTouchHandler *>(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
Expand Down
23 changes: 23 additions & 0 deletions ios/utils/UIView+RNSUtility.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#import <Foundation/Foundation.h>

#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTSurfaceTouchHandler.h>
#else
#import <React/RCTTouchHandler.h>
#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
55 changes: 55 additions & 0 deletions ios/utils/UIView+RNSUtility.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

#import "UIView+RNSUtility.h"

#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTSurfaceView.h>
#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<RNS_TOUCH_HANDLER_ARCH_TYPE *>(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<RNS_TOUCH_HANDLER_ARCH_TYPE *>(touchHandler);
}

#endif // RCT_NEW_ARCH_ENABLED

return nil;
}

@end

0 comments on commit 431fd22

Please sign in to comment.