Issue #2794 - Fixing iPad iOS 11 Tab Bar Bottom Behavior (#3041)

* Fixing iPad iOS 11 Tab Bar Bottom Behavior with changing widths via Multitasking

* Updating snapshot tests

* Changes per discussion in !3041

* Additional changes per discussion in !3041

* Changing to test for constrained height

* Additional changes per discussion in !3041
This commit is contained in:
SteffeyDev 2018-01-08 21:28:13 -05:00 committed by Spencer Carli
parent e2e540c32d
commit c7b73cd8b4
3 changed files with 217 additions and 117 deletions

View File

@ -105,68 +105,81 @@ exports[`TabNavigator renders successfully 1`] = `
"alignItems": "center", "alignItems": "center",
"backgroundColor": "rgba(0, 0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)",
"flex": 1, "flex": 1,
"justifyContent": "flex-end",
} }
} }
testID={undefined} testID={undefined}
> >
<View <View
style={ style={
Object { Array [
"flexGrow": 1, Object {
} "alignItems": "center",
"flex": 1,
},
Object {
"flexDirection": "column",
"justifyContent": "flex-end",
},
undefined,
]
} }
> >
<View <View
collapsable={undefined}
style={ style={
Object { Object {
"alignItems": "center", "flexGrow": 1,
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
} }
} }
/> >
<View <View
collapsable={undefined}
style={
Object {
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
collapsable={undefined}
style={
Object {
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
</View>
<Text
accessible={true}
allowFontScaling={true}
collapsable={undefined} collapsable={undefined}
ellipsizeMode="tail"
style={ style={
Object { Object {
"alignItems": "center", "backgroundColor": "transparent",
"bottom": 0, "color": "rgba(52, 120, 246, 1)",
"justifyContent": "center", "fontSize": 10,
"left": 0, "marginBottom": 1.5,
"opacity": 0, "textAlign": "center",
"position": "absolute",
"right": 0,
"top": 0,
} }
} }
/> >
Welcome anonymous
</Text>
</View> </View>
<Text
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
style={
Object {
"backgroundColor": "transparent",
"color": "rgba(52, 120, 246, 1)",
"fontSize": 10,
"marginBottom": 1.5,
"marginLeft": 0,
"marginTop": 0,
"textAlign": "center",
}
}
>
Welcome anonymous
</Text>
</View> </View>
</View> </View>
</View> </View>

View File

@ -8,10 +8,12 @@ import {
View, View,
Platform, Platform,
Keyboard, Keyboard,
Dimensions,
} from 'react-native'; } from 'react-native';
import TabBarIcon from './TabBarIcon'; import TabBarIcon from './TabBarIcon';
import SafeAreaView from '../SafeAreaView'; import SafeAreaView from '../SafeAreaView';
import withOrientation from '../withOrientation'; import withOrientation from '../withOrientation';
import type { Layout } from 'react-native-tab-view/src/TabViewTypeDefinitions';
import type { import type {
NavigationRoute, NavigationRoute,
@ -51,11 +53,16 @@ type Props = {
tabStyle?: ViewStyleProp, tabStyle?: ViewStyleProp,
showIcon?: boolean, showIcon?: boolean,
isLandscape: boolean, isLandscape: boolean,
layout: Layout,
adaptive: boolean,
}; };
const majorVersion = parseInt(Platform.Version, 10); const majorVersion = parseInt(Platform.Version, 10);
const isIos = Platform.OS === 'ios'; const isIos = Platform.OS === 'ios';
const useHorizontalTabs = majorVersion >= 11 && isIos; const isIOS11 = majorVersion >= 11 && isIos;
const isTablet =
Dimensions.get('window').height / Dimensions.get('window').width < 1.6;
const defaultMaxTabBarItemWidth = 125;
class TabBarBottom extends React.PureComponent<Props> { class TabBarBottom extends React.PureComponent<Props> {
// See https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UITabBar.html // See https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UITabBar.html
@ -67,6 +74,7 @@ class TabBarBottom extends React.PureComponent<Props> {
showLabel: true, showLabel: true,
showIcon: true, showIcon: true,
allowFontScaling: true, allowFontScaling: true,
adaptive: isIOS11,
}; };
_renderLabel = (scene: TabScene) => { _renderLabel = (scene: TabScene) => {
@ -99,19 +107,18 @@ class TabBarBottom extends React.PureComponent<Props> {
const tintColor = scene.focused ? activeTintColor : inactiveTintColor; const tintColor = scene.focused ? activeTintColor : inactiveTintColor;
const label = this.props.getLabel({ ...scene, tintColor }); const label = this.props.getLabel({ ...scene, tintColor });
let marginLeft = 0;
if (isLandscape && showIcon && useHorizontalTabs) {
marginLeft = LABEL_LEFT_MARGIN;
}
let marginTop = 0;
if (!isLandscape && showIcon && useHorizontalTabs) {
marginTop = LABEL_TOP_MARGIN;
}
if (typeof label === 'string') { if (typeof label === 'string') {
return ( return (
<Animated.Text <Animated.Text
style={[styles.label, { color, marginLeft, marginTop }, labelStyle]} style={[
styles.label,
{ color },
showIcon && this._shouldUseHorizontalTabs()
? styles.labelBeside
: styles.labelBeneath,
labelStyle,
]}
allowFontScaling={allowFontScaling} allowFontScaling={allowFontScaling}
> >
{label} {label}
@ -147,7 +154,7 @@ class TabBarBottom extends React.PureComponent<Props> {
inactiveTintColor={inactiveTintColor} inactiveTintColor={inactiveTintColor}
renderIcon={renderIcon} renderIcon={renderIcon}
scene={scene} scene={scene}
style={showLabel && useHorizontalTabs ? {} : styles.icon} style={showLabel && this._shouldUseHorizontalTabs() ? {} : styles.icon}
/> />
); );
}; };
@ -158,6 +165,65 @@ class TabBarBottom extends React.PureComponent<Props> {
return testIDProps; return testIDProps;
}; };
_tabItemMaxWidth() {
const { tabStyle, layout } = this.props;
let maxTabBarItemWidth;
const flattenedTabStyle = StyleSheet.flatten(tabStyle);
if (flattenedTabStyle) {
if (typeof flattenedTabStyle.width === 'number') {
maxTabBarItemWidth = flattenedTabStyle.width;
} else if (
typeof flattenedTabStyle.width === 'string' &&
flattenedTabStyle.endsWith('%')
) {
const width = parseFloat(flattenedTabStyle.width);
if (Number.isFinite(width)) {
maxTabBarItemWidth = layout.width * (width / 100);
}
} else if (typeof flattenedTabStyle.maxWidth === 'number') {
maxTabBarItemWidth = flattenedTabStyle.maxWidth;
} else if (
typeof flattenedTabStyle.maxWidth === 'string' &&
flattenedTabStyle.endsWith('%')
) {
const width = parseFloat(flattenedTabStyle.maxWidth);
if (Number.isFinite(width)) {
maxTabBarItemWidth = layout.width * (width / 100);
}
}
}
if (!maxTabBarItemWidth) {
maxTabBarItemWidth = defaultMaxTabBarItemWidth;
}
return maxTabBarItemWidth;
}
_shouldUseHorizontalTabs() {
const { routes } = this.props.navigation.state;
const { isLandscape, layout, adaptive, tabStyle } = this.props;
if (!adaptive) {
return false;
}
let tabBarWidth = layout.width;
if (tabBarWidth === 0) {
return isTablet;
}
const isHeightConstrained = layout.height < 500;
if (isHeightConstrained) {
return isLandscape;
} else {
const maxTabBarItemWidth = this._tabItemMaxWidth();
return routes.length * maxTabBarItemWidth <= tabBarWidth;
}
}
render() { render() {
const { const {
position, position,
@ -171,17 +237,20 @@ class TabBarBottom extends React.PureComponent<Props> {
animateStyle, animateStyle,
tabStyle, tabStyle,
isLandscape, isLandscape,
layout,
} = this.props; } = this.props;
const { routes } = navigation.state; const { routes } = navigation.state;
const previousScene = routes[navigation.state.index]; const previousScene = routes[navigation.state.index];
// Prepend '-1', so there are always at least 2 items in inputRange // Prepend '-1', so there are always at least 2 items in inputRange
const inputRange = [-1, ...routes.map((x: *, i: number) => i)]; const inputRange = [-1, ...routes.map((x: *, i: number) => i)];
const isHeightConstrained =
layout.height === 0 ? !isTablet : layout.height < 500;
const tabBarStyle = [ const tabBarStyle = [
styles.tabBar, styles.tabBar,
isLandscape && useHorizontalTabs this._shouldUseHorizontalTabs() && isHeightConstrained
? styles.tabBarLandscape ? styles.tabBarCompact
: styles.tabBarPortrait, : styles.tabBarRegular,
style, style,
]; ];
@ -220,17 +289,19 @@ class TabBarBottom extends React.PureComponent<Props> {
? onPress({ previousScene, scene, jumpToIndex }) ? onPress({ previousScene, scene, jumpToIndex })
: jumpToIndex(index)} : jumpToIndex(index)}
> >
<Animated.View <Animated.View style={[styles.tab, { backgroundColor }]}>
style={[ <View
styles.tab, style={[
isLandscape && useHorizontalTabs && styles.tabLandscape, styles.tab,
!isLandscape && useHorizontalTabs && styles.tabPortrait, this._shouldUseHorizontalTabs()
{ backgroundColor }, ? styles.tabLandscape
tabStyle, : styles.tabPortrait,
]} tabStyle,
> ]}
{this._renderIcon(scene)} >
{this._renderLabel(scene)} {this._renderIcon(scene)}
{this._renderLabel(scene)}
</View>
</Animated.View> </Animated.View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
); );
@ -241,8 +312,6 @@ class TabBarBottom extends React.PureComponent<Props> {
} }
} }
const LABEL_LEFT_MARGIN = 20;
const LABEL_TOP_MARGIN = 15;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBar: { tabBar: {
backgroundColor: '#F7F7F7', // Default background color in iOS 10 backgroundColor: '#F7F7F7', // Default background color in iOS 10
@ -250,16 +319,15 @@ const styles = StyleSheet.create({
borderTopColor: 'rgba(0, 0, 0, .3)', borderTopColor: 'rgba(0, 0, 0, .3)',
flexDirection: 'row', flexDirection: 'row',
}, },
tabBarLandscape: { tabBarCompact: {
height: 29, height: 29,
}, },
tabBarPortrait: { tabBarRegular: {
height: 49, height: 49,
}, },
tab: { tab: {
flex: 1, flex: 1,
alignItems: isIos ? 'center' : 'stretch', alignItems: isIos ? 'center' : 'stretch',
justifyContent: 'flex-end',
}, },
tabPortrait: { tabPortrait: {
justifyContent: 'flex-end', justifyContent: 'flex-end',
@ -274,9 +342,15 @@ const styles = StyleSheet.create({
}, },
label: { label: {
textAlign: 'center', textAlign: 'center',
backgroundColor: 'transparent',
},
labelBeneath: {
fontSize: 10, fontSize: 10,
marginBottom: 1.5, marginBottom: 1.5,
backgroundColor: 'transparent', },
labelBeside: {
fontSize: 13,
marginLeft: 20,
}, },
}); });

View File

@ -61,68 +61,81 @@ exports[`TabBarBottom renders successfully 1`] = `
"alignItems": "center", "alignItems": "center",
"backgroundColor": "rgba(0, 0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)",
"flex": 1, "flex": 1,
"justifyContent": "flex-end",
} }
} }
testID={undefined} testID={undefined}
> >
<View <View
style={ style={
Object { Array [
"flexGrow": 1, Object {
} "alignItems": "center",
"flex": 1,
},
Object {
"flexDirection": "column",
"justifyContent": "flex-end",
},
undefined,
]
} }
> >
<View <View
collapsable={undefined}
style={ style={
Object { Object {
"alignItems": "center", "flexGrow": 1,
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
} }
} }
/> >
<View <View
collapsable={undefined}
style={
Object {
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
collapsable={undefined}
style={
Object {
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
</View>
<Text
accessible={true}
allowFontScaling={true}
collapsable={undefined} collapsable={undefined}
ellipsizeMode="tail"
style={ style={
Object { Object {
"alignItems": "center", "backgroundColor": "transparent",
"bottom": 0, "color": "rgba(52, 120, 246, 1)",
"justifyContent": "center", "fontSize": 10,
"left": 0, "marginBottom": 1.5,
"opacity": 0, "textAlign": "center",
"position": "absolute",
"right": 0,
"top": 0,
} }
} }
/> >
s1
</Text>
</View> </View>
<Text
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
style={
Object {
"backgroundColor": "transparent",
"color": "rgba(52, 120, 246, 1)",
"fontSize": 10,
"marginBottom": 1.5,
"marginLeft": 0,
"marginTop": 0,
"textAlign": "center",
}
}
>
s1
</Text>
</View> </View>
</View> </View>
</View> </View>