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

Weird SafeAreaView Behavior on iOS when used with KeyboardToolbar #809

Open
RodSarhan opened this issue Feb 15, 2025 · 11 comments
Open

Weird SafeAreaView Behavior on iOS when used with KeyboardToolbar #809

RodSarhan opened this issue Feb 15, 2025 · 11 comments
Assignees
Labels
🐛 bug Something isn't working 🏭 fabric Changes specific to new (fabric/jsi) architecture 🍎 iOS iOS specific KeyboardToolbar Anything related to KeyboardToolbar component repro provided Issue contains reproduction repository/code

Comments

@RodSarhan
Copy link
Contributor

Describe the bug
SafeAreaView from react-native-safe-area-context doesn't respect safe areas on iOS when used alongside KeyboardToolbar in the same screen.

I found this hard to debug with hot reloads, to reproduce this issue i needed to reload the app between code changes for some reason.

Notes:

  • This only happens with SafeAreaView component, as useSafeAreaInsets hook still reports the insets correctly.
  • I was able to fix this by wrapping my screen with SafeAreaProvider (other than the one in the root of the app) and mounting KeyboardToolbar outside of it. but i don't feel good about this solution.

I'm not sure if this is intended behavior and to which repo it should be reported, so excuse me if it's not a valid issue.

Code snippet
Working (red color is visible under the status and navigation bars):

return (
    <>
        <SafeAreaView style={{flex: 1, backgroundColor: 'red'}}>
            <View style={{flex: 1, backgroundColor: 'blue'}} />
        </SafeAreaView>
        {/* <KeyboardToolbar /> */}
    </>
);

Working (red color is visible under the status and navigation bars):

return (
    <>
        <SafeAreaProvider>
            <SafeAreaView style={{flex: 1, backgroundColor: 'red'}}>
                <View style={{flex: 1, backgroundColor: 'blue'}} />
            </SafeAreaView>
        </SafeAreaProvider>
        <KeyboardToolbar />
    </>
);

Not Working (The blue view takes over the full screen):

return (
    <>
        <SafeAreaView style={{flex: 1, backgroundColor: 'red'}}>
            <View style={{flex: 1, backgroundColor: 'blue'}} />
        </SafeAreaView>
        <KeyboardToolbar />
    </>
);

To Reproduce
Steps to reproduce the behavior:

  1. Mount a screen that has both SafeAreaView and KeyboardToolbar
  2. SafeAreaView renders its contents under the "Unsafe" area.

Expected behavior
Using KeyboardToolbar shouldn't cause safe area insets to be ignored.

Screenshots
Image

Image

Smartphone (please complete the following information):

  • Device: iPhone 16 (Simulator)
  • OS: iOS 18.0
  • RN version: 0.76.7
  • RN architecture: new arch
  • JS engine: Hermes
  • Library version: 1.16.3 (seen this on earlier versions too)

Additional context
"react-native-safe-area-context": "5.2.0"
"react-native-edge-to-edge": "1.2.0"

@kirillzyusko kirillzyusko added 🐛 bug Something isn't working 🍎 iOS iOS specific KeyboardToolbar Anything related to KeyboardToolbar component labels Feb 16, 2025
@kirillzyusko
Copy link
Owner

@RodSarhan This is how it works in my example project:

Image

And

Image

Once I'm adding SafeAreaProvider everything starts to work 🤔 Am I doing something wrong?

@RodSarhan
Copy link
Contributor Author

@kirillzyusko I'll try to reproduce it in the example app and let you know

@kirillzyusko
Copy link
Owner

@RodSarhan okay, thank you ❤️

@RodSarhan
Copy link
Contributor Author

@kirillzyusko So i was able to reproduce the issue consistently on "FabricExample" but not in "example"

In App.tsx just add this instead of the existing app content and you should be able to see it, but keep in mind that you need to refresh the app (or unmount + mount the screen?) instead of relying on hot reload

const Stack = createStackNavigator();

const defaultScreenOptions = {
  headerShown: false,
  gestureEnabled: false,
  cardStyle: { backgroundColor: "#fff" },
};

const ExampleWorkingScreen = () => {
  return (
    <>
      <SafeAreaProvider>
        <SafeAreaView style={{ flex: 1, backgroundColor: "red" }}>
          <View style={{ flex: 1, backgroundColor: "blue" }} />
        </SafeAreaView>
      </SafeAreaProvider>
      <KeyboardToolbar />
    </>
  );
};

const ExampleWorkingScreen2 = () => {
  return (
    <>
      <SafeAreaView style={{ flex: 1, backgroundColor: "red" }}>
        <View style={{ flex: 1, backgroundColor: "blue" }} />
      </SafeAreaView>
    </>
  );
};

const ExampleBuggyScreen = () => {
  return (
    <>
      <SafeAreaView style={{ flex: 1, backgroundColor: "red" }}>
        <View style={{ flex: 1, backgroundColor: "blue" }} />
      </SafeAreaView>
      <KeyboardToolbar />
    </>
  );
};

export default function App() {
  return (
    <SafeAreaProvider initialMetrics={initialWindowMetrics}>
      <GestureHandlerRootView style={styles.root}>
        <KeyboardProvider statusBarTranslucent>
          <NavigationContainer fallback={spinner} linking={linking}>
            <Stack.Navigator
              initialRouteName="example"
              screenOptions={defaultScreenOptions}
            >
              <Stack.Screen component={ExampleWorkingScreen} name="example" />
            </Stack.Navigator>
          </NavigationContainer>
        </KeyboardProvider>
      </GestureHandlerRootView>
    </SafeAreaProvider>
  );
}

@kirillzyusko kirillzyusko added the 🏭 fabric Changes specific to new (fabric/jsi) architecture label Feb 16, 2025
@kirillzyusko
Copy link
Owner

Thank you @RodSarhan for providing full example and sorry for a long response ❤

I managed to reproduce this problem without react-native-keyboard-controller:

import "react-native-gesture-handler";

import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import * as React from "react";
import { SafeAreaView, StyleSheet, View } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import Reanimated, { useAnimatedStyle } from "react-native-reanimated";
import {
  SafeAreaProvider,
  initialWindowMetrics,
} from "react-native-safe-area-context";

const styles = StyleSheet.create({
  root: {
    flex: 1,
  },
});

const Stack = createStackNavigator();

const defaultScreenOptions = {
  headerShown: false,
  gestureEnabled: false,
  cardStyle: { backgroundColor: "#fff" },
};

const ExampleBuggyScreen = () => {
  const style = useAnimatedStyle(
    () => ({
      transform: [{ translateY: 0 }],
    }),
    [],
  );

  return (
    <>
      <SafeAreaView style={{ flex: 1, backgroundColor: "red" }}>
        <View style={{ flex: 1, backgroundColor: "blue" }} />
      </SafeAreaView>
      <Reanimated.View style={style} />
    </>
  );
};

export default function App() {
  return (
    <SafeAreaProvider initialMetrics={initialWindowMetrics}>
      <GestureHandlerRootView style={styles.root}>
        <NavigationContainer>
          <Stack.Navigator
            initialRouteName="example"
            screenOptions={defaultScreenOptions}
          >
            <Stack.Screen component={ExampleBuggyScreen} name="example" />
          </Stack.Navigator>
        </NavigationContainer>
      </GestureHandlerRootView>
    </SafeAreaProvider>
  );
}

I think it's a bug in react-native-safe-area-context. Basically Reanimated.View + translateY: 0 style makes this bug to be 100% reproducible. At this stage I'm not sure if it's reanimated bug (with plain View or static style the bug is not reproducible) or safe-area-context bug. So I think the best thing here is to open an issue in these two repositories (you can use example that I provided).

Does it make sense what I'm saying? Will you open the issue? 👀

@kirillzyusko kirillzyusko added the repro provided Issue contains reproduction repository/code label Mar 4, 2025
@RodSarhan
Copy link
Contributor Author

RodSarhan commented Mar 4, 2025

Thank you very much for your support.

Yeah I'll open an issue there and show them this example, cheers!

I'm facing another issue with KeyboardToolbar and KeyboardAwareScrollView, where the next and previous button presses get ignored until i scroll a bit or focus another input then they start working again.
This only happens on android and fabric, and only on specific phones (mine is samsung s23).
I fixed it by using custom buttons made with TouchableOpacity which worked for some reason, but i don't even know how to start debugging or reproducing this niche case.

It might also be not related to this repo, so i won't open an issue till i figure it out.

@kirillzyusko
Copy link
Owner

I'm facing another issue with KeyboardToolbar and KeyboardAwareScrollView, where the next and previous button presses get ignored until i scroll a bit or focus another input then they start working again.
This only happens on android and fabric, and only on specific phones (mine is samsung s23).
I fixed it by using custom buttons made with TouchableOpacity which worked for some reason, but i don't even know how to start debugging or reproducing this niche case.

So it looks like it's a problem of TouchableNativeFeedback, right?..

Yeah I'll open an issue there and show them this example, cheers!

I think SafeAreaContext maintainers are not going to look into this problem 😔 Do you think it would be reasonable to create an issue in reanimated repository?

@kdwkr
Copy link

kdwkr commented Mar 10, 2025

I think Reanimated is causing this problem, not because of react-native-safe-area-context.

import * as React from 'react';
import { SafeAreaView, View } from 'react-native';
import Reanimated, { useAnimatedStyle } from 'react-native-reanimated';

const App = () => {
  const style = useAnimatedStyle(
    () => ({
      transform: [{ translateY: 0 }],
    }),
    [],
  );
  return (
    <View style={{ flex: 1 }}>
      <SafeAreaView style={{ flex: 1, backgroundColor: 'red' }}>
        <View style={{ flex: 1, backgroundColor: 'blue' }} />
      </SafeAreaView>
      <Reanimated.View style={style} />
    </View>
  );
};

export default App;

This example code also having problem.

@kdwkr
Copy link

kdwkr commented Mar 10, 2025

Created issue on Reanimated repository
software-mansion/react-native-reanimated#7220

@kirillzyusko
Copy link
Owner

Thank you @kdwkr !

@RodSarhan
Copy link
Contributor Author

Was a bit busy the past week sorry, thanks for reporting it 🫡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working 🏭 fabric Changes specific to new (fabric/jsi) architecture 🍎 iOS iOS specific KeyboardToolbar Anything related to KeyboardToolbar component repro provided Issue contains reproduction repository/code
Projects
None yet
Development

No branches or pull requests

3 participants