diff --git a/docs/docs/docs/guides/handling-scrollview-insets.md b/docs/docs/docs/guides/handling-scrollview-insets.md index 64c2126d..6671baaa 100644 --- a/docs/docs/docs/guides/handling-scrollview-insets.md +++ b/docs/docs/docs/guides/handling-scrollview-insets.md @@ -7,3 +7,43 @@ If you expirence issues with ScrollView content being below the tab bar, add `co {/* content */} ``` + +## Keyboard-avoiding fixed content on iOS + +On iPhone, tab scenes extend behind the tab bar, including the Liquid Glass tab +bar on iOS 26+. A bottom-aligned control inside `KeyboardAvoidingView` therefore +needs to reserve the tab bar height while the keyboard is closed. Keep the +full-screen background on an outer view and apply the offset only to the +keyboard-avoiding content. The following pattern is for a visible native bottom +bar without a bottom accessory or `minimizeBehavior`: + +```tsx +import { useBottomTabBarHeight } from 'react-native-bottom-tabs'; +import { KeyboardAvoidingView, Platform, View } from 'react-native'; + +function Screen() { + const tabBarHeight = useBottomTabBarHeight(); + const hasNativeBottomTabBar = + Platform.OS === 'ios' && !Platform.isPad && !Platform.isTV; + + return ( + + + {/* Bottom-aligned content */} + + + ); +} +``` + +Do not apply this offset to the entire tab scene: scenes intentionally extend +behind translucent tab bars, and globally shortening them would also double +offsets already based on `useBottomTabBarHeight`. Custom, hidden, minimized, +sidebar, and accessory layouts should use spacing derived from their own visible +bottom element instead of this fixed-content pattern. diff --git a/packages/example-shared/src/App.tsx b/packages/example-shared/src/App.tsx index 420d3427..fa347da9 100644 --- a/packages/example-shared/src/App.tsx +++ b/packages/example-shared/src/App.tsx @@ -46,6 +46,7 @@ import CustomTabBar from './Examples/CustomTabBar'; import NativeBottomTabsTabBarHidden from './Examples/NativeBottomTabsTabBarHidden'; import OriginalIconColors from './Examples/OriginalIconColors'; import NativeBottomTabsOriginalIcons from './Examples/NativeBottomTabsOriginalIcons'; +import KeyboardAvoidingViewExample from './Examples/KeyboardAvoidingView'; import { useLogger } from '@react-navigation/devtools'; import LazyTabs from './Examples/LazyTabs'; import { LogBox } from 'react-native'; @@ -198,6 +199,11 @@ const examples = [ component: NativeBottomTabsOriginalIcons, name: 'Native Bottom Tabs with original icons', }, + { + component: KeyboardAvoidingViewExample, + name: 'Keyboard Avoiding View', + screenOptions: { headerShown: false }, + }, { component: NativeBottomTabs, name: 'Native Bottom Tabs' }, { component: JSBottomTabs, name: 'JS Bottom Tabs' }, { diff --git a/packages/example-shared/src/Examples/KeyboardAvoidingView.tsx b/packages/example-shared/src/Examples/KeyboardAvoidingView.tsx new file mode 100644 index 00000000..bf026439 --- /dev/null +++ b/packages/example-shared/src/Examples/KeyboardAvoidingView.tsx @@ -0,0 +1,102 @@ +import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'; +import { + KeyboardAvoidingView, + Platform, + StyleSheet, + Text, + TextInput, + View, +} from 'react-native'; +import { useBottomTabBarHeight } from 'react-native-bottom-tabs'; + +const Tab = createNativeBottomTabNavigator(); + +function HomeScreen() { + const tabBarHeight = useBottomTabBarHeight(); + const hasNativeBottomTabBar = + Platform.OS === 'ios' && !Platform.isPad && !Platform.isTV; + + return ( + + + + KeyboardAvoidingView + Bottom Tabs + + + + + + ); +} + +function SettingsScreen() { + return ( + + Settings + + ); +} + +export default function KeyboardAvoidingViewExample() { + return ( + + require('../../assets/icons/article_dark.png'), + }} + /> + require('../../assets/icons/grid_dark.png'), + }} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#fff', + }, + keyboardAvoidingView: { + flex: 1, + }, + content: { + flex: 1, + padding: 20, + justifyContent: 'flex-end', + }, + title: { + fontSize: 20, + fontWeight: 'bold', + marginBottom: 10, + }, + spacer: { + flex: 1, + }, + input: { + borderWidth: 1, + borderColor: '#555', + borderRadius: 8, + padding: 15, + fontSize: 16, + }, +});