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

How to get the height of bottom navigator? #7359

Closed
vsyw opened this issue Feb 4, 2019 · 31 comments
Closed

How to get the height of bottom navigator? #7359

vsyw opened this issue Feb 4, 2019 · 31 comments

Comments

@vsyw
Copy link

vsyw commented Feb 4, 2019

I'm trying to get the height of the bottom tab bar so that I know how to absolutely position a component above it without that component being cut off.

@brentvatne
Copy link
Member

there are several factors that impact the height: 1) is it compact or full height tab bar? this depends on whether landscape/portrait and whether you're using a tablet or phone. if compact, the base height is 29, otherwise it's 49 2) is there a notch/home indicator on the device? if so, need to add the safe area height to the base height 3) is the device a new ipad pro? if so, safe area height is different.

so this is not something that you probably want to actually handle yourself. i think it is worth adding an option to the tab navigator that allows you to pass in a callback for when the height is available / when it changes.

@brentvatne brentvatne transferred this issue from react-navigation/react-navigation Feb 4, 2019
@brentvatne
Copy link
Member

@satya164 - maybe useful for material-bottom-tabs and top-tabs too

@satya164
Copy link
Member

satya164 commented Feb 4, 2019

I think currently if you use the TopTabBar component etc. and pass onLayout prop it should work to get the height. If not we should support it :)

Regarding the use case of getting the height for positioning something above it, I'd advise against that because layout is async, so whatever you want to render won't be rendered correctly before the callback is called. I'd do something like:

tabBarComponent: props => (
  <View>
    <SomethingAbsolute />
    <TopTabBar {...props} />
  </View>
);

@brentvatne
Copy link
Member

Regarding the use case of getting the height for positioning something above it, I'd advise against that because layout is async, so whatever you want to render won't be rendered correctly before the callback is called.

I think in the worst case where you want the tab bar and the overlay to be rendered at the same time there will be a frame or so of delay, wouldn't be ideal yeah. If you're going to animate it in or show it at any time after the initial render of the tab bar it's not a bad compromise though

@draperunner
Copy link

Found a way to solve this, using onLayout and measureInWindow, which seems to work fairly consistently on different Android devices and iPhones. It's a bit hacky and involves Redux. If you could find a simpler way to support this, that would be great! :D

I created the following connected TabBar component. I could not pass onLayout directly to BottomTabBar, so I wrapped it in a View.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
import { BottomTabBar } from 'react-navigation';

class TabBarComponent extends Component {
  measure = () => {
    if (this.tabBar) {
      this.tabBar.measureInWindow(this.props.setTabMeasurement);
    }
  }

  render() {
    return (
      <View
        ref={(el) => { this.tabBar = el; }}
        onLayout={this.measure}
      >
        <BottomTabBar {...this.props} />
      </View>
    );
  }
}

function mapDispatchToProps(dispatch) {
  return {
    setTabMeasurement: (x, y, width, height) => dispatch({
      type: 'SET_TAB_MEASUREMENT',
      measurement: {
        x, y, width, height,
      },
    }),
  };
}

export default connect(null, mapDispatchToProps)(TabBarComponent);

I could then access the layout/measurement in the absolutely positioned component through the Redux state, and calculate its y position (top) with the formula measurement.y - measurement.height - <height of component> - <desired margin to tab bar>

@donholly
Copy link

@draperunner Thanks a lot for posting this solution! I was trying to avoid having to duplicate this logic everywhere that needed the TabBar height for keyboard avoidance calculations. While I'd really like to see this make its way into the library, this is a pretty good solution for keeping track of the state of the Tab Bar's dimensions for now. I'll Likely end up doing the same thing to get the height of the HeaderBar for Stack Navigators.

Thanks again!

@JanithaR
Copy link

JanithaR commented May 6, 2019

@brentvatne

so this is not something that you probably want to actually handle yourself. i think it is worth adding an option to the tab navigator that allows you to pass in a callback for when the height is available / when it changes.

Is there an ETA on this?

@brentvatne
Copy link
Member

no

@tungduonghgg123
Copy link

tungduonghgg123 commented May 13, 2019

"How could you @brentvatne . You have the map to the haven and never show us the way."
Why don't you just provide us a method to get the height and position of the Bottom Tab Bar? You probably know it because the Tab Bar need it to show up.

@mjmaix
Copy link

mjmaix commented May 29, 2019

@draperunner I rewrote your solution using React Context

import React, { Component } from 'react';
import { View, LayoutChangeEvent, LayoutRectangle } from 'react-native';
import { BottomTabBar, BottomTabBarProps } from 'react-navigation';

interface State {
  layout: LayoutRectangle;
}

const initialValue = {
  x: 0,
  y: 0,
  width: 0,
  height: 0,
};

const TabBarContext = React.createContext<LayoutRectangle>(initialValue);
export const TabBarProvider = TabBarContext.Provider;
export const TabBarConsumer = TabBarContext.Consumer;

class TabBarComponent extends Component<BottomTabBarProps, State> {
  public readonly state = {
    layout: initialValue,
  };
  private setTabMeasurement = (event: LayoutChangeEvent) => {
    const {
      nativeEvent: { layout },
    } = event;
    this.setState({
      layout,
    });
  };

  public render() {
    return (
      <TabBarProvider value={this.state.layout}>
        <View onLayout={this.setTabMeasurement}>
          <BottomTabBar {...this.props} />
        </View>
      </TabBarProvider>
    );
  }
}

export { TabBarComponent };
import React, { ReactNode } from 'react';
import { ScrollView, ScrollViewProps } from 'react-native';
import { TabBarConsumer } from './TabBarComponent';

export const TabBarAwareScrollView = ({
  children,
  ...props
}: ScrollViewProps & { children: ReactNode }) => {
  return (
    <TabBarConsumer>
      {tabBarLayout => (
        <ScrollView
          {...props}
          contentContainerStyle={{
            paddingBottom: tabBarLayout.height,
          }}
        >
          {children}
        </ScrollView>
      )}
    </TabBarConsumer>
  );
};

Screen Shot 2019-05-29 at 5 54 29 PM

Screen Shot 2019-05-29 at 5 54 26 PM

Screen Shot 2019-05-29 at 5 54 36 PM

@IceTeddy
Copy link

IceTeddy commented Aug 12, 2019

Forgive my poor English.Forgive me for using Google Translator.
I calculated the height.

`

getHeaderHeight(event)
{
let {height} = event.nativeEvent.layout;
this.headerHeight = height;
}`

`<View style={{ flex: 1 }} onLayout={this.getContentHeight}>

getContentHeight(event)
{
let {height} = event.nativeEvent.layout;
this.contentHeight = height;
}
`

So~
`let {width, height} = Dimensions.get('window');

let bottomBarHeight = height - this.headerHeight - this.contentHeight;`

@sturmenta
Copy link

In "react-navigation": "3.11.0", I set this with

const createBottomTabNavigator(
  ...,
  {
    tabBarOptions: {
      style: {
        padding: 10,
        height: 80,
      },
    },
  },
);

https://reactnavigation.org/docs/en/bottom-tab-navigator.html

@osdnk
Copy link
Member

osdnk commented Oct 11, 2019

This issue appears to be no longer valid

@osdnk osdnk closed this as completed Oct 11, 2019
@leonardo2204
Copy link

@osdnk is there a final solution to do what is proposed in this issue?
Thanks

@pie6k
Copy link

pie6k commented Oct 24, 2019

I came with solution without context etc.

import React from 'react';
import { View } from 'react-native';
import { BottomTabBar } from 'react-navigation-tabs';
import { BottomTabBarProps } from 'react-navigation-tabs/lib/typescript/src/types';

// layout is stored as module variable
let tabBarLayout = {
  x: 0,
  y: 0,
  width: 0,
  height: 0,
};

// there is exported way to get current tabbar height
export function getTabBarHeight() {
  return tabBarLayout.height;
}

// there is simple tab bar component used when creating navigator that will update this layout
export function TabBarComponent(props: BottomTabBarProps) {
  return (
    <View
      collapsable={false}
      onLayout={(event) => {
        tabBarLayout = event.nativeEvent.layout;
      }}
    >
      <BottomTabBar {...props} />
    </View>
  );
}

Later on, I simply use this component, when creating navigator

const HomeScreenTabs = createBottomTabNavigator(
  {
    ...calendarRoutes,
    ...taskScreenRoutes,
  },
  {
    tabBarComponent: TabBarComponent,
);

and then, I can just get tabbat height anywhere using exported getTabBarHeight()

@satya164 satya164 transferred this issue from react-navigation/tabs Feb 24, 2020
@domingogogo
Copy link

in react navigation V5 I saw this one in source code.
image

@FilDevTronic
Copy link

Huge if true, @domingogogo

@im-amir
Copy link

im-amir commented Aug 19, 2020

Default height is 49. Search for the DEFAULT_HEIGHT in below link:

https://github.com/react-navigation/tabs/blob/d2aa789109ed0df61c0c0ac7b759ac386a720804/src/views/BottomTabBar.js#L239

@chrisngabp
Copy link

Modern solutions in React Navigation 5 (from documentation: https://reactnavigation.org/docs/bottom-tab-navigator/)

To get the height of the bottom tab bar, you can use BottomTabBarHeightContext with React's Context API or useBottomTabBarHeight:


import { BottomTabBarHeightContext } from '@react-navigation/bottom-tabs';

// ...

<BottomTabBarHeightContext.Consumer>
  {tabBarHeight => (
    /* render something */
  )}
</BottomTabBarHeightContext.Consumer>

Or you can use:


import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';

// ...

const tabBarHeight = useBottomTabBarHeight();

@gsavvid
Copy link

gsavvid commented Dec 18, 2020

Module '"../../../../node_modules/@react-navigation/bottom-tabs/lib/typescript/src"' has no exported member 'BottomTabBarHeightContext'.
Module '"../../../../node_modules/@react-navigation/bottom-tabs/lib/typescript/src"' has no exported member 'useBottomTabBarHeight'.

using:

"@react-navigation/bottom-tabs": "^5.8.0",
"@react-navigation/native": "^5.7.3"

UPDATE:
Updating to "@react-navigation/bottom-tabs": "^5.11.2" fixed the issue.

@WeraG
Copy link

WeraG commented Dec 21, 2020

Hey, I'm using version "@react-navigation/bottom-tabs": "^5.11.2" and having following errors.
When using useBottomTabBarHeight I get: TypeError: (0 , _bottomTabs.useBottomTabBarHeight) is not a function.
When using BottomTabBarHeightContext I get: TypeError: Cannot read property 'Consumer' of undefined.
Any help would be much appreciated!

@devinkg
Copy link

devinkg commented Mar 13, 2021

I came with solution without context etc.

import React from 'react';
import { View } from 'react-native';
import { BottomTabBar } from 'react-navigation-tabs';
import { BottomTabBarProps } from 'react-navigation-tabs/lib/typescript/src/types';

// layout is stored as module variable
let tabBarLayout = {
  x: 0,
  y: 0,
  width: 0,
  height: 0,
};

// there is exported way to get current tabbar height
export function getTabBarHeight() {
  return tabBarLayout.height;
}

// there is simple tab bar component used when creating navigator that will update this layout
export function TabBarComponent(props: BottomTabBarProps) {
  return (
    <View
      collapsable={false}
      onLayout={(event) => {
        tabBarLayout = event.nativeEvent.layout;
      }}
    >
      <BottomTabBar {...props} />
    </View>
  );
}

Later on, I simply use this component, when creating navigator

const HomeScreenTabs = createBottomTabNavigator(
  {
    ...calendarRoutes,
    ...taskScreenRoutes,
  },
  {
    tabBarComponent: TabBarComponent,
);

and then, I can just get tabbat height anywhere using exported getTabBarHeight()

This worked for me, Thanks a lot !

@adajoy
Copy link

adajoy commented Apr 30, 2021

react hook solution:

import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
const bottomTabBarHeight = useBottomTabBarHeight();

@jjhiggz
Copy link

jjhiggz commented Aug 10, 2021

I spent hours on this and here's some gotchas associated with this. I should note that in my context, I was specifically using the materialNavBar, and I found the documentation to be terrible, and couldn't find another more useful thread.

  1. If you are using Material NavBar, I personally wouldn't go and override its height. It is a responsively designed thing, and it is designed well (just has horrible documentation) so if you start overwriting the default height you wind up losing alot of the benefits that you get from it.

  2. You may read on threads that a Material Nav Bar always has a height of 64. This is confusing and took me a minute. Let's say you are on an Iphone 10, there is a space under the tab bar that is colored along with the tab bar. That height depends on the phone that you are viewing on. For example on a iPhone 8, it's smaller than an Iphone 10. You can calculate that area by getting the "Bottom" key on the safe area insets. But know that the means of calculating that will probably change as react native does.

  3. Ultimately for me, the code that I wrote wound up being really simple, after 5 hours of hating myself. They probably should include something like this in their library. Here's what I had.

import React from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";

export default function useMaterialNavBarHeight() {
  const { bottom } = useSafeAreaInsets();
  const tabBarHeight = bottom + 64;

  return tabBarHeight;
}

Then in a component where you need the navbar height, you can just do.

function someComponent(){
   const navBarHeight = useMaterialNavBarHeight()
}

@github-actions
Copy link

Hey! This issue is closed and isn't watched by the core team. You are welcome to discuss the issue with others in this thread, but if you think this issue is still valid and needs to be tracked, please open a new issue with a repro.

@Stevemoretz
Copy link

I spent hours on this and here's some gotchas associated with this. I should note that in my context, I was specifically using the materialNavBar, and I found the documentation to be terrible, and couldn't find another more useful thread.

  1. If you are using Material NavBar, I personally wouldn't go and override its height. It is a responsively designed thing, and it is designed well (just has horrible documentation) so if you start overwriting the default height you wind up losing alot of the benefits that you get from it.
  2. You may read on threads that a Material Nav Bar always has a height of 64. This is confusing and took me a minute. Let's say you are on an Iphone 10, there is a space under the tab bar that is colored along with the tab bar. That height depends on the phone that you are viewing on. For example on a iPhone 8, it's smaller than an Iphone 10. You can calculate that area by getting the "Bottom" key on the safe area insets. But know that the means of calculating that will probably change as react native does.
  3. Ultimately for me, the code that I wrote wound up being really simple, after 5 hours of hating myself. They probably should include something like this in their library. Here's what I had.
import React from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";

export default function useMaterialNavBarHeight() {
  const { bottom } = useSafeAreaInsets();
  const tabBarHeight = bottom + 64;

  return tabBarHeight;
}

Then in a component where you need the navbar height, you can just do.

function someComponent(){
   const navBarHeight = useMaterialNavBarHeight()
}

Are you sure? 64 + insets.bottom gives 98 on iPhone X, that sounds totally wrong.

@Stevemoretz
Copy link

You probably meant 54 instead ;)

@jjhiggz
Copy link

jjhiggz commented Aug 16, 2021

I spent hours on this and here's some gotchas associated with this. I should note that in my context, I was specifically using the materialNavBar, and I found the documentation to be terrible, and couldn't find another more useful thread.

  1. If you are using Material NavBar, I personally wouldn't go and override its height. It is a responsively designed thing, and it is designed well (just has horrible documentation) so if you start overwriting the default height you wind up losing alot of the benefits that you get from it.
  2. You may read on threads that a Material Nav Bar always has a height of 64. This is confusing and took me a minute. Let's say you are on an Iphone 10, there is a space under the tab bar that is colored along with the tab bar. That height depends on the phone that you are viewing on. For example on a iPhone 8, it's smaller than an Iphone 10. You can calculate that area by getting the "Bottom" key on the safe area insets. But know that the means of calculating that will probably change as react native does.
  3. Ultimately for me, the code that I wrote wound up being really simple, after 5 hours of hating myself. They probably should include something like this in their library. Here's what I had.
import React from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";

export default function useMaterialNavBarHeight() {
  const { bottom } = useSafeAreaInsets();
  const tabBarHeight = bottom + 64;

  return tabBarHeight;
}

Then in a component where you need the navbar height, you can just do.

function someComponent(){
   const navBarHeight = useMaterialNavBarHeight()
}

Are you sure? 64 + insets.bottom gives 98 on iPhone X, that sounds totally wrong.

You may very well be right. I think I took 64px that I read on other threads and assumed it was the same. I really only needed it for one place on my app so I can't confirm or deny because there is a padding under that fixed element anyways. So it may do exactly what I need as far as responsiveness goes while being exactly 10 units off. Do you have any sources that confirm the 54. If so I'll edit my comment.

@Stevemoretz
Copy link

Stevemoretz commented Aug 16, 2021

I spent hours on this and here's some gotchas associated with this. I should note that in my context, I was specifically using the materialNavBar, and I found the documentation to be terrible, and couldn't find another more useful thread.

  1. If you are using Material NavBar, I personally wouldn't go and override its height. It is a responsively designed thing, and it is designed well (just has horrible documentation) so if you start overwriting the default height you wind up losing alot of the benefits that you get from it.
  2. You may read on threads that a Material Nav Bar always has a height of 64. This is confusing and took me a minute. Let's say you are on an Iphone 10, there is a space under the tab bar that is colored along with the tab bar. That height depends on the phone that you are viewing on. For example on a iPhone 8, it's smaller than an Iphone 10. You can calculate that area by getting the "Bottom" key on the safe area insets. But know that the means of calculating that will probably change as react native does.
  3. Ultimately for me, the code that I wrote wound up being really simple, after 5 hours of hating myself. They probably should include something like this in their library. Here's what I had.
import React from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";

export default function useMaterialNavBarHeight() {
  const { bottom } = useSafeAreaInsets();
  const tabBarHeight = bottom + 64;

  return tabBarHeight;
}

Then in a component where you need the navbar height, you can just do.

function someComponent(){
   const navBarHeight = useMaterialNavBarHeight()
}

Are you sure? 64 + insets.bottom gives 98 on iPhone X, that sounds totally wrong.

You may very well be right. I think I took 64px that I read on other threads and assumed it was the same. I really only needed it for one place on my app so I can't confirm or deny because there is a padding under that fixed element anyways. So it may do exactly what I need as far as responsiveness goes while being exactly 10 units off. Do you have any sources that confirm the 54. If so I'll edit my comment.

Yeah sometimes 10px doesn't really show itself, here's one demonstration :
https://stackoverflow.com/a/60895652/10268067

I tested it on iPhone X using the debug inspect element it's 88 pixels, insets.bottom is 34 therefore 54 is exactly right for iPhone X.

Here's the full code I wrote for both default and material versions:

//this part is picked from a stackoverflow issue.

const getDefaultHeaderHeight = (orientation: Orientation) => {
    const majorVersion = parseInt(<string>Platform.Version, 10);
    const isIos = Platform.OS === "ios";
    const isIOS11 = majorVersion >= 11 && isIos;
    // @ts-ignore
    if (Platform.isPad) return 49;
    if (isIOS11 && orientation === Orientation.portrait) return 49;
    return 29;
};

export const useBottomTabBarHeight = () => {
    const {orientation, insets} = useSomeHookWhichWouldGetOrientationAndInsetsForYouSorryICouldNotShareMine();
    let type = BOTTOM_NAVIGATOR_TYPE.DEFAULT;
    if (Platform.OS === "android") {
        type = BOTTOM_NAVIGATOR_TYPE.MATERIAL;
    }
    if (type === BOTTOM_NAVIGATOR_TYPE.DEFAULT) {
        return getDefaultHeaderHeight(orientation) + insets.bottom;
    } else if (type === BOTTOM_NAVIGATOR_TYPE.MATERIAL) {
       //based on https://stackoverflow.com/a/60895652/10268067
        return 54 + insets.bottom;
    }
};

usage :

const height = useBottomTabBarHeight();

@DaveLomber
Copy link

There is a new library which works like a charm

https://github.com/ConnectyCube/react-native-android-navbar-height

import { Dimensions } from "react-native";
import { getNavigationBarHeight } from "react-native-android-navbar-height";

// ...

const scale = Dimensions.get('screen').scale;
const navigationBarHeight = await getNavigationBarHeight();
const result = navigationBarHeight / scale;

@FDiskas
Copy link

FDiskas commented Mar 28, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests