diff --git a/FabricTestExample/App.js b/FabricTestExample/App.js index eec0fb0e8d..49db6e6bdd 100644 --- a/FabricTestExample/App.js +++ b/FabricTestExample/App.js @@ -92,6 +92,7 @@ import Test1844 from './src/Test1844'; import Test1864 from './src/Test1864'; import TestScreenAnimation from './src/TestScreenAnimation'; import Test1981 from './src/Test1981'; +import Test2008 from './src/Test2008'; enableFreeze(true); diff --git a/FabricTestExample/src/Test2008.tsx b/FabricTestExample/src/Test2008.tsx new file mode 100644 index 0000000000..ceb556a351 --- /dev/null +++ b/FabricTestExample/src/Test2008.tsx @@ -0,0 +1,184 @@ +import React from 'react'; +import { Text, View, SafeAreaView, StyleSheet, Pressable } from 'react-native'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import { + NavigationContainer, + useNavigation, + ParamListBase, + type NavigationProp, +} from '@react-navigation/native'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; + +const styles = StyleSheet.create({ + inner: { + flex: 1, + backgroundColor: 'white', + }, + safeArea: { + flex: 1, + backgroundColor: 'red', + }, + innerModal: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.4)', + justifyContent: 'center', + alignItems: 'center', + }, + pressable: { + padding: 20, + backgroundColor: '#ccc', + marginVertical: 5, + }, + + textIntro: { + padding: 10, + }, + + buttons: { + flexDirection: 'row', + padding: 10, + }, + + text: { + textAlign: 'center', + }, +}); + +type RootStackScreens = { + Home: undefined; + Modal: undefined; + TransparentModal: undefined; + ContainedTransparentModal: undefined; +}; + +const RootStack = createNativeStackNavigator(); + +function Home() { + const navigation = useNavigation>(); + return ( + + + + Red represents the safe area padding as provided by React Native Safe + Area Context (although I've noticed that the issue also affects the + build in react native SafeArea component). + + + This only applies to iOS. Ensure you have rotation lock off, and + rotate the view into landscape orientation. Note how the red safe + areas appear. Then tap `Spawn Transparent Modal`, dismiss the modal, + and then rotate the screen again to see how the safe areas are now + stuck as the portrait values. You must force quite the app to undo the + bug. + + navigation.navigate('Modal')}> + "modal" + + navigation.navigate('ContainedTransparentModal')}> + "containedTransparentModal" + + navigation.navigate('TransparentModal')}> + "transparentModal" + + + + ); +} + +function Modal({ + navigation, +}: { + navigation: NativeStackNavigationProp; +}) { + return ( + + Modal + navigation.goBack()}> + Go Back! + + navigation.push('Modal')}> + Open another modal! + + + ); +} + +function TransparentModal({ + navigation, +}: { + navigation: NativeStackNavigationProp; +}) { + return ( + + Transparent Modal + navigation.goBack()}> + Go Back! + + navigation.push('TransparentModal')}> + Open another modal! + + + ); +} + +function ContainedTransparentModal({ + navigation, +}: { + navigation: NativeStackNavigationProp; +}) { + return ( + + Contained Transparent Modal + navigation.goBack()}> + Go Back! + + navigation.push('ContainedTransparentModal')}> + Open another modal! + + + ); +} + +export default function App() { + return ( + + + + + + + + + + + ); +} diff --git a/TestsExample/App.js b/TestsExample/App.js index 90d1085d49..4a064f1293 100644 --- a/TestsExample/App.js +++ b/TestsExample/App.js @@ -94,6 +94,7 @@ import Test1829 from './src/Test1829'; import Test1844 from './src/Test1844'; import Test1864 from './src/Test1864'; import Test1981 from './src/Test1981'; +import Test2008 from './src/Test2008'; enableFreeze(true); diff --git a/TestsExample/src/Test2008.tsx b/TestsExample/src/Test2008.tsx new file mode 100644 index 0000000000..ceb556a351 --- /dev/null +++ b/TestsExample/src/Test2008.tsx @@ -0,0 +1,184 @@ +import React from 'react'; +import { Text, View, SafeAreaView, StyleSheet, Pressable } from 'react-native'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import { + NavigationContainer, + useNavigation, + ParamListBase, + type NavigationProp, +} from '@react-navigation/native'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; + +const styles = StyleSheet.create({ + inner: { + flex: 1, + backgroundColor: 'white', + }, + safeArea: { + flex: 1, + backgroundColor: 'red', + }, + innerModal: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.4)', + justifyContent: 'center', + alignItems: 'center', + }, + pressable: { + padding: 20, + backgroundColor: '#ccc', + marginVertical: 5, + }, + + textIntro: { + padding: 10, + }, + + buttons: { + flexDirection: 'row', + padding: 10, + }, + + text: { + textAlign: 'center', + }, +}); + +type RootStackScreens = { + Home: undefined; + Modal: undefined; + TransparentModal: undefined; + ContainedTransparentModal: undefined; +}; + +const RootStack = createNativeStackNavigator(); + +function Home() { + const navigation = useNavigation>(); + return ( + + + + Red represents the safe area padding as provided by React Native Safe + Area Context (although I've noticed that the issue also affects the + build in react native SafeArea component). + + + This only applies to iOS. Ensure you have rotation lock off, and + rotate the view into landscape orientation. Note how the red safe + areas appear. Then tap `Spawn Transparent Modal`, dismiss the modal, + and then rotate the screen again to see how the safe areas are now + stuck as the portrait values. You must force quite the app to undo the + bug. + + navigation.navigate('Modal')}> + "modal" + + navigation.navigate('ContainedTransparentModal')}> + "containedTransparentModal" + + navigation.navigate('TransparentModal')}> + "transparentModal" + + + + ); +} + +function Modal({ + navigation, +}: { + navigation: NativeStackNavigationProp; +}) { + return ( + + Modal + navigation.goBack()}> + Go Back! + + navigation.push('Modal')}> + Open another modal! + + + ); +} + +function TransparentModal({ + navigation, +}: { + navigation: NativeStackNavigationProp; +}) { + return ( + + Transparent Modal + navigation.goBack()}> + Go Back! + + navigation.push('TransparentModal')}> + Open another modal! + + + ); +} + +function ContainedTransparentModal({ + navigation, +}: { + navigation: NativeStackNavigationProp; +}) { + return ( + + Contained Transparent Modal + navigation.goBack()}> + Go Back! + + navigation.push('ContainedTransparentModal')}> + Open another modal! + + + ); +} + +export default function App() { + return ( + + + + + + + + + + + ); +} diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index 123eee6447..a4cbfe27ae 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -1226,15 +1226,28 @@ - (UIViewController *)findChildVCForConfigAndTrait:(RNSWindowTrait)trait includi UIViewController *lastViewController = [[self childViewControllers] lastObject]; if ([self.presentedViewController isKindOfClass:[RNSScreen class]]) { lastViewController = self.presentedViewController; + + if (!includingModals) { + return nil; + } + // we don't want to allow controlling of status bar appearance when we present non-fullScreen modal // and it is not possible if `modalPresentationCapturesStatusBarAppearance` is not set to YES, so even // if we went into a modal here and ask it, it wouldn't take any effect. For fullScreen modals, the system // asks them by itself, so we can stop traversing here. // for screen orientation, we need to start the search again from that modal - return !includingModals - ? nil - : [(RNSScreen *)lastViewController findChildVCForConfigAndTrait:trait includingModals:includingModals] - ?: lastViewController; + UIViewController *modalOrChild = [(RNSScreen *)lastViewController findChildVCForConfigAndTrait:trait + includingModals:includingModals]; + if (modalOrChild != nil) { + return modalOrChild; + } + + // if searched VC was not found, we don't want to search for configs of child VCs any longer, + // and we don't want to rely on lastViewController. + // That's because the modal did not find a child VC that has an orientation set, + // and it doesn't itself have an orientation set. Hence, we fallback to the standard behavior. + // Please keep in mind that this behavior might be wrong and could lead to undiscovered bugs. + // For more information, see https://github.com/software-mansion/react-native-screens/pull/2008. } UIViewController *selfOrNil = [self hasTraitSet:trait] ? self : nil;