Skip to content

Commit

Permalink
fix: try to apply pointer events behaviors in overlay (#1582)
Browse files Browse the repository at this point in the history
## Description

PR adding support for `pointerEvents` behaviors in `FullWindowOverlay`
component.

## Test code and steps to reproduce

`Test1096.tsx`

## Checklist

- [ ] Included code example that can be used to test this change
- [ ] Updated TS types
- [ ] Updated documentation: <!-- For adding new props to native-stack
-->
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [ ] Ensured that CI passes
  • Loading branch information
WoLewicki authored Nov 14, 2022
1 parent aa27b7b commit 02d7311
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 15 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ To take advantage of the native stack navigator primitive for React Navigation t
- for React Navigation v5 to the [README in react-native-screens/native-stack](https://github.com/software-mansion/react-native-screens/tree/main/native-stack)
- for older versions to the [README in react-native-screens/createNativeStackNavigator](https://github.com/software-mansion/react-native-screens/tree/main/createNativeStackNavigator)

## `FullWindowOverlay`

Native `iOS` component for rendering views straight under the `Window`. Based on `RCTPerfMonitor`. You should treat it as a wrapper, providing full-screen, transparent view which receives no props and should ideally render one child `View`, being the root of its view hierarchy. For the example usage, see https://github.com/software-mansion/react-native-screens/blob/main/TestsExample/src/Test1096.tsx

## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)

React-native-navigation library already uses native containers for rendering navigation scenes so wrapping these scenes with `<ScreenContainer>` or `<Screen>` component does not provide any benefits. Yet if you would like to build a component that uses screens primitives under the hood (for example a view pager component) it is safe to use `<ScreenContainer>` and `<Screen>` components for that as these work out of the box when rendered on react-native-navigation scenes.
Expand Down
8 changes: 5 additions & 3 deletions TestsExample/src/Test1096.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ function Home({
/>
</View>
</Modal>
<FullWindowOverlay style={{position: 'absolute', width: '100%', height: '100%', justifyContent: 'center'}}>
<View style={styles.box} />
<Button title="click me" onPress={() => console.warn('clicked')} />
<FullWindowOverlay>
<View style={{flex: 1, justifyContent: 'center'}} pointerEvents="box-none">
<View style={styles.box} />
<Button title="click me" onPress={() => console.warn('clicked')} />
</View >
</FullWindowOverlay>
</View>
);
Expand Down
32 changes: 32 additions & 0 deletions ios/RNSFullWindowOverlay.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,38 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
return NO;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL canReceiveTouchEvents = ([self isUserInteractionEnabled] && ![self isHidden]);
if (!canReceiveTouchEvents) {
return nil;
}

// `hitSubview` is the topmost subview which was hit. The hit point can
// be outside the bounds of `view` (e.g., if -clipsToBounds is NO).
UIView *hitSubview = nil;
BOOL isPointInside = [self pointInside:point withEvent:event];
if (![self clipsToBounds] || isPointInside) {
// Take z-index into account when calculating the touch target.
NSArray<UIView *> *sortedSubviews = [self reactZIndexSortedSubviews];

// The default behaviour of UIKit is that if a view does not contain a point,
// then no subviews will be returned from hit testing, even if they contain
// the hit point. By doing hit testing directly on the subviews, we bypass
// the strict containment policy (i.e., UIKit guarantees that every ancestor
// of the hit view will return YES from -pointInside:withEvent:). See:
// - https://developer.apple.com/library/ios/qa/qa2013/qa1812.html
for (UIView *subview in [sortedSubviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
hitSubview = [subview hitTest:convertedPoint withEvent:event];
if (hitSubview != nil) {
break;
}
}
}
return hitSubview;
}

@end

@implementation RNSFullWindowOverlay {
Expand Down
30 changes: 20 additions & 10 deletions src/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react';
import React, { PropsWithChildren, ReactNode } from 'react';
import {
Animated,
Image,
ImageProps,
Platform,
requireNativeComponent,
StyleProp,
StyleSheet,
UIManager,
View,
ViewProps,
ViewStyle,
} from 'react-native';
import { Freeze } from 'react-freeze';
import { version } from 'react-native/package.json';
Expand Down Expand Up @@ -84,7 +86,9 @@ let NativeScreenStackHeaderSubview: React.ComponentType<React.PropsWithChildren<
>>;
let AnimatedNativeScreen: React.ComponentType<ScreenProps>;
let NativeSearchBar: React.ComponentType<SearchBarProps>;
let NativeFullWindowOverlay: React.ComponentType<View>;
let NativeFullWindowOverlay: React.ComponentType<PropsWithChildren<{
style: StyleProp<ViewStyle>;
}>>;

const ScreensNativeModules = {
get NativeScreen() {
Expand Down Expand Up @@ -337,6 +341,19 @@ function ScreenContainer(props: ScreenContainerProps) {
return <View {...rest} />;
}

function FullWindowOverlay(props: { children: ReactNode }) {
if (Platform.OS !== 'ios') {
console.warn('Importing FullWindowOverlay is only valid on iOS devices.');
return <View {...props} />;
}
return (
<ScreensNativeModules.NativeFullWindowOverlay
style={{ position: 'absolute', width: '100%', height: '100%' }}>
{props.children}
</ScreensNativeModules.NativeFullWindowOverlay>
);
}

const styles = StyleSheet.create({
headerSubview: {
position: 'absolute',
Expand Down Expand Up @@ -431,6 +448,7 @@ module.exports = {
ScreenContext,
ScreenStack,
InnerScreen,
FullWindowOverlay,

get NativeScreen() {
return ScreensNativeModules.NativeScreen;
Expand Down Expand Up @@ -460,14 +478,6 @@ module.exports = {

return ScreensNativeModules.NativeSearchBar;
},
get FullWindowOverlay() {
if (Platform.OS !== 'ios') {
console.warn('Importing FullWindowOverlay is only valid on iOS devices.');
return View;
}

return ScreensNativeModules.NativeFullWindowOverlay;
},
// these are functions and will not be evaluated until used
// so no need to use getters for them
ScreenStackHeaderBackButtonImage,
Expand Down
6 changes: 4 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { ReactNode } from 'react';
import { Animated, View, ViewProps, ImageProps, Image } from 'react-native';
import {
ScreenProps,
Expand Down Expand Up @@ -74,7 +74,9 @@ export const NativeScreenNavigationContainer: React.ComponentType<ScreenContaine

export const ScreenStack: React.ComponentType<ScreenStackProps> = View;

export const FullWindowOverlay = View;
export const FullWindowOverlay = View as React.ComponentType<{
children: ReactNode;
}>;

export const ScreenStackHeaderBackButtonImage = (
props: ImageProps
Expand Down

1 comment on commit 02d7311

@mvolonnino
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, been diving deep into creating a toast component inside my native stacks, I am using the FullWindowOverlay, but my panResponders / touch events are not firing. Trying to utilize this FullWindowOverlay it looks like you are working on to be able to show my Toast notifications over the NativeStack Modal. It works in doing so, and whatever stack I currently on, while wrapping my Toast component with FullWindowOverlay, the Toast shows above the current stack, whether that is my normal BottomTab stack or Modal stack. The only issue im running into is not being able to register any gestures / taps on my actual Toast component when it is wrapped inside the FullWindowOverlay. I am however able to touch and scroll on the Stack that is currently being shown under the FullWindowOverlay. Any ideas as to why this is happening? I can also provide code snippets if that helps.

Thank you for taking the time to read this and hopefully be able to help me out!

Please sign in to comment.