Skip to content

Instantly share code, notes, and snippets.

@StevePotter
Created October 9, 2017 04:26
Show Gist options
  • Save StevePotter/09e66f2c790161cf218043f28f8ba5fc to your computer and use it in GitHub Desktop.
Save StevePotter/09e66f2c790161cf218043f28f8ba5fc to your computer and use it in GitHub Desktop.
// @flow
/*
A simple spacer component to give room for the status bar.
Thanks to https://github.com/jgkim/react-native-status-bar-size/blob/master/StatusBarSizeIOS.js
*/
/* eslint-disable react-native/split-platform-components */
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { View, StatusBar, StatusBarIOS, NativeModules, Platform, StyleSheet } from 'react-native'
const { StatusBarManager } = NativeModules
type State = {
statusBarHeight?: number
}
type Props = {
style?: string | StyleSheet.Styles
}
// currently all devices use a 20px height by default. It increases to 40x during calls. We call StatusBarManager.getHeight but there's a slight chance its callback will execute after render, so this is used in that rare case
const DefaultIosStatusBarHeight = 20
function isDefined(val) {
return typeof val !== 'undefined'
}
class StatusBarSpacer extends Component {
static displayName = 'StatusBarSpacer'
state: State
props: Props
statusBarChangedListener: ?(height: number) => void
static propTypes = {
style: PropTypes.any
};
constructor(props: Props) {
super(props)
this.state = {}
}
// only ios apps draw under the status bar
appDrawsUnderStatusBar() {
return Platform.OS === 'ios'
}
componentDidMount() {
if (!this.appDrawsUnderStatusBar()) return
// https://github.com/facebook/react-native/blob/a609d1c2b78fb2493935b065afa52413313ea199/Libraries/Components/StatusBar/StatusBar.js#L175
if (StatusBarIOS && !this.statusBarChangedListener) {
this.statusBarChangedListener = StatusBarIOS.addListener('statusBarFrameDidChange', (statusBarData) => {
this.setStatusBarHeight(statusBarData.frame.height)
})
}
this.updateHeightIfNecessary(this.state)
}
componentWillUnmount() {
if (this.statusBarChangedListener) {
this.statusBarChangedListener.remove()
this.statusBarChangedListener = null
}
}
shouldComponentUpdate(nextProps: Props, nextState: State) {
if (!this.appDrawsUnderStatusBar()) return false
return this.state.statusBarHeight !== nextState.statusBarHeight || !isDefined(StatusBar.currentHeight) || StatusBar.currentHeight !== nextState.statusBarHeight
}
componentWillUpdate(nextProps: Props, nextState: State) {
this.updateHeightIfNecessary(nextState)
}
setStatusBarHeight(statusBarHeight: number) {
this.setState({ statusBarHeight })
}
updateHeightIfNecessary(state: State) {
if (!this.appDrawsUnderStatusBar()) return
if (isDefined(StatusBar.currentHeight)) {
if (StatusBar.currentHeight !== state.statusBarHeight) {
this.setStatusBarHeight(StatusBar.currentHeight)
}
} else if (StatusBarManager && StatusBarManager.getHeight) {
StatusBarManager.getHeight(({ height }) => {
if (this.statusBarChangedListener && height !== state.statusBarHeight) {
this.setStatusBarHeight(height)
}
})
}
}
render() {
if (!this.appDrawsUnderStatusBar()) return null
// android does not draw under the status bar so we ignore it
const height = isDefined(this.state.statusBarHeight) ? this.state.statusBarHeight : DefaultIosStatusBarHeight
// it is possible to render once before getting the height back from StatusBarManager
return <View style={[this.props.style, { height }]} />
}
}
export default StatusBarSpacer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment