react-native-safe-area-context icon indicating copy to clipboard operation
react-native-safe-area-context copied to clipboard

Header overlaying statusbar IOS after screenorientation and setting visibility of statusbar

Open tanteemmie opened this issue 3 years ago • 4 comments

Report the issue also here as requested in the issue reported here: https://github.com/react-navigation/react-navigation/issues/8425

Current Behavior

We have a profile view as stackview within a tabbarview. In profile you can edit your profile photo by taking a photo. We use Expo camera and set the StatusBar hidden when the camera is active. All the other views display the statusbar. The screen is rotating freely when the device is rotating. Going a stack screen back after hiding the statusbar, and then display it again, causes the header to overlay the statusbar

2020-06-11 15_03_33-Clipboard

This is the code simplyfied.

RootNavigation.tsx

<NavigationContainer>
    <Tab.Navigator initialRouteName="App">
        <Tab.Screen name="More" component="MoreNavigation" />
    </Tab.Navigator>
</NavigationContainer>

MoreNavigation.tsx

<Stack.Navigator initialRouteName="More">
    <Stack.Screen name="UserProfile" component={Profile} options={{ headerTitle: 'Profile' }} />
    <Stack.Screen name="EditProfilePhoto" component={EditProfilePhoto} 
                           options={{ headerTitle: '', headerShown: false }} />
</Stack.Navigator>

Profile.tsx

<View style={styles.container}>
          <ScrollView
            refreshControl={
              <RefreshControl
                refreshing={this.state.isRefreshing}
                onRefresh={this.onRefresh}
                tintColor={Theme.buttonBackgroundColor}
                colors={[Theme.buttonBackgroundColor]}
              />
            }
          >
    <View style={styles.avatar}>
        <Avatar 
                showEditButton
                editButton={{
                    name: 'md-create',
                    type: 'ionicon',
                    underlayColor: '#aaa',
                    size: 35,
                    iconStyle: { fontSize: 25 }
                  }}
                onEditPress={() => this.props.navigation.navigate('EditProfilePhoto')} />
    </View>
</View>

EditProfilePhoto.tsx

render() {
    return <CameraView onPhotoSelected={(imageUri: string) => this.onPhotoSelected(imageUri)} />;
  }

CameraView.tsx

close = async () => {
    StatusBar.setHidden(false);
    const popAction = StackActions.pop(1);
    RootNavigationModule.dispatch(popAction);
  };

render() {
return (
      <View style={styles.container}>
        <Camera
          ref={this.camera}
          style={{ aspectRatio: screenOrientation === 'Portrait' ? 3 / 4 : 4 / 3 }}
          type={cameraType}
          autoFocus={Camera.Constants.AutoFocus.on}
          zoom={zoom}
          flashMode={this.state.cameraFlash}
        >
          <StatusBar hidden />
          <View style={styles.header}>
            <View style={styles.headerIcons}>
              <TouchableOpacity onPress={() => this.close()}>
                <Icon name="close" type="material-community" size={30} color="#fff" />
              </TouchableOpacity>
              <TouchableOpacity
                onPress={() => this.switchCameraFlash()}
                disabled={cameraType !== Camera.Constants.Type.back}
              >
                <Icon
                  name={cameraFlashText}
                  type="material-community"
                  size={25}
                  color={cameraType === Camera.Constants.Type.back ? '#fff' : 'transparent'}
                />
              </TouchableOpacity>
            </View>
          </View>
        </Camera>
        <View style={styles.footer}>
          <View style={styles.footerIcons}>
            <TouchableOpacity onPress={() => this.pickImage()}>
              <Icon name="photo" type="fontawesome" size={40} color="#fff" />
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.takePhoto()}>
              <Icon name="circle" type="entypo" size={75} color="#fff" />
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.switchCameraType()}>
              <Icon name="ios-reverse-camera" type="ionicon" size={40} color="#fff" />
            </TouchableOpacity>
          </View>
        </View>
      </View>
);
}

Expected Behavior

Header not overlaying the statusbar.

How to reproduce

When the camera is active, the statusbar is hidden, and we have our phone in portrait Change the phone to landscape, and change it back to portrait. Close the camera were you go back 1 stack screen. The header set in the navigation is overlaying the statusbar, while the statusbar is visible.

When we have the camera in landscape and close the camera and go back 1 stack screen, there is no statusbar, while it is default behaviour for IOS. When we then hold our phone in portrait outside of the camera all is well.

Also during overlaying the statusbar, then turning the phone to landscape and back to portrait, the header is correctly adjusted to the statusbar.

Your Environment

software version
iOS or Android IOS 13
@react-navigation/native ^5.0.9
@react-navigation/stack ^5.5.1
react-native-gesture-handler ~1.6.0
react-native-safe-area-context 0.7.3
react-native-screens ~2.2.0
react-native 37.0.0
expo 37.0.0
node 12.10.0
npm or yarn 1.22.4

tanteemmie avatar Jan 18 '21 08:01 tanteemmie

I have the same error, i test on device iphone 7

tudiantuan avatar Jan 30 '21 02:01 tudiantuan

Any update on this issue?

tanteemmie avatar Mar 24 '21 12:03 tanteemmie

Hitting the same issue when using the statusBarHidden API from createNativeStackNavigator() in react-navigation... Setting statusBarHidden to true when rotating an iPad to landscape, then to false again upon rotating back to portrait causes the StatusBar to overlap the App content.

It appears the issue is that the top value from safe area insets is 0 after hiding/showing the status bar.

I created a sample React Native project that reproduces the issue here. To repro, simply run the app on an iPad, rotate to landscape and then back to portrait.

I was able to work around the issue by saving the top value from useSafeAreaInsets() in a separate piece of state and then use this state if/when top ever moves to 0.

const {top} = useSafeAreaInsets();
const [originalTop, setOriginalTop] = useState(top);

useEffect(() => {
  if (top > 0 && originalTop !== top) {
    setOriginalTop(top);
  }
}, [originalTop, top]);

return (
  <SafeAreaView style={{flex: 1}}>
    <View style={{flex: 1, marginTop: top === 0 ? originalTop : undefined}}>
      {...}
    </View>
  </SafeAreaView>
);

mrbrentkelly avatar Nov 05 '21 17:11 mrbrentkelly

Hitting the same issue when using the statusBarHidden API from createNativeStackNavigator() in react-navigation... Setting statusBarHidden to true when rotating an iPad to landscape, then to false again upon rotating back to portrait causes the StatusBar to overlap the App content.

It appears the issue is that the top value from safe area insets is 0 after hiding/showing the status bar.

I created a sample React Native project that reproduces the issue here. To repro, simply run the app on an iPad, rotate to landscape and then back to portrait.

I was able to work around the issue by saving the top value from useSafeAreaInsets() in a separate piece of state and then use this state if/when top ever moves to 0.

const {top} = useSafeAreaInsets();
const [originalTop, setOriginalTop] = useState(top);

useEffect(() => {
  if (top > 0 && originalTop !== top) {
    setOriginalTop(top);
  }
}, [originalTop, top]);

return (
  <SafeAreaView style={{flex: 1}}>
    <View style={{flex: 1, marginTop: top === 0 ? originalTop : undefined}}>
      {...}
    </View>
  </SafeAreaView>
);

Yes, it's a workaround, but in my project with hundreds of pages, i cann't just solve it by this. Actually , i met this problem not by using statusBarHidden api. I face it in such a situation: There is a TextInput near the bottom of this page, when focus the TextInput, the page scrolls to let the TextInput show( which is expected----I don't know why the behavior differs in Expo which will not automatically scroll). But when blurs, the top sometimes (maybe 50% chance) becomes 0 instead of 20 in my Iphone which let header overlaying statusbar. This is only happenning on ios. The pseudo code is like:

const insets = useSafeAreaInsets(); // for console only
return (
    <SafeAreaView style={{flex: 1, backgroundColor: 'green'}}>
            <Text>This is the title</Text>
            <View style={{height: 500}}/>
            <TextInput style={{backgroundColor: 'blue'}}/>
            <Text>H2</Text>
        </SafeAreaView>
)

Console.log insets will tell that the top changes between 20 and 0 when focus/blur the TextInput.

coolguy001tv avatar Feb 15 '22 03:02 coolguy001tv

I also get this problem, for me it only seems to happen on iPhones without a notch/island, currently the iPhone SE 3rd Gen.

unstableair avatar Dec 20 '22 11:12 unstableair

This is for v3 of the library, and we're on v4. Please re-open a new issue if you're still facing issues

jacobp100 avatar Jan 19 '23 16:01 jacobp100

I experience same issue with newest version of library on iPhone SE 3rd Gen.

ecosse3 avatar Jan 20 '23 09:01 ecosse3

Is it the same as https://github.com/th3rdwave/react-native-safe-area-context/issues/23 ?

jacobp100 avatar Jan 20 '23 09:01 jacobp100

Is it the same as #23 ?

Not sure, it's related to status bar as well but maybe it's not worth to mention this problem in this topic. Safe Area just doesn't work on iPhone SE but on any other iOS phone works correctly.

image

ecosse3 avatar Jan 20 '23 10:01 ecosse3

Could you make an issue for Android only? This issue is muddled both with the iOS and Android

jacobp100 avatar Jan 20 '23 11:01 jacobp100