Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: try to apply pointer events behaviors in overlay #1582

Merged
merged 7 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,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