I've followed the example here and then tried to add a header. I've managed to get the title to show the key and the back navigation to show the key for the previous card in the stack.
The problem I have is that I cannot figure out how to align the icon and text for the back action. If I add flow: 1
to the style then it just disappears. If I add any align option then nothing changes. Funnilly enough, changing flexDirection works, as does changing the margins for left and right.
Right now, the icon and text appear to be aligned along the top edge.
I'm not sure if it's a text issue or an issue with the size of the container. Note that I've tried textAlignVertical: 'center'
on the text to no avail.
How do I do this?
Does anyone know how to solve?
The code in question:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*
* Refer: http://facebook.github.io/react-native/docs/navigation.html
*/
import React, { Component } from 'react';
import {
AppRegistry,
NavigationExperimental,
PixelRatio,
ScrollView,
StyleSheet,
View,
Text,
TouchableHighlight
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons'
const {
CardStack: NavigationCardStack,
StateUtils: NavigationStateUtils,
Header: NavigationHeader,
} = NavigationExperimental;
export default class BleedingEdgeApplication extends Component {
constructor(props, context) {
super(props, context);
this.state = {
navigationState: {
index: 0,
routes: [{key: 'Home'}]
}
}
this._onNavigationChange = this._onNavigationChange.bind(this);
}
_onNavigationChange(type) {
// extract the navigation state from the current state
let {navigationState} = this.state;
switch(type) {
case 'push':
// push a new route, which in our case is an object with a key value
const route = {key: ':' + Date.now()};
// use the push reducer provided by NavigationStateUtils
navigationState = NavigationStateUtils.push(navigationState, route);
break;
case 'pop':
// Pop the current route using the pop reducer
navigationState = NavigationStateUtils.pop(navigationState);
break;
}
if (this.state.navigationState !== navigationState) {
this.setState({navigationState});
}
}
render() {
return (
<MyVerySimpleNavigator
navigationState={this.state.navigationState}
onNavigationChange={this._onNavigationChange}
onExit={this._exit}
/>
);
}
}
class TappableRow extends Component {
render () {
return (
<TouchableHighlight
style={styles.row}
underlayColor="#D0D0D0"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>
{this.props.text}
</Text>
</TouchableHighlight>
);
}
}
class MyVeryComplexScene extends Component {
render() {
return (
<ScrollView style={styles.scrollView}>
<Text style={styles.row}>
Route: {this.props.route.key}
</Text>
<TappableRow
text="Tap me to load the next scene"
onPress={this.props.onPushRoute}
/>
<TappableRow
text="Tap me to go back"
onPress={this.props.onPopRoute}
/>
</ScrollView>
);
}
}
class MyVerySimpleNavigator extends Component {
constructor(props, context) {
super(props, context);
this._onPushRoute = this.props.onNavigationChange.bind(null, 'push');
this._onPopRoute = this.props.onNavigationChange.bind(null, 'pop');
this._renderScene = this._renderScene.bind(this);
this._renderHeader = this._renderHeader.bind(this);
this._renderLeftHeader = this._renderLeftHeader.bind(this);
}
render() {
return (
<NavigationCardStack
onNavigateBack={this._onPopRoute}
navigationState={this.props.navigationState}
renderScene={this._renderScene}
renderHeader={this._renderHeader}
style={styles.navigator}
/>
);
}
_renderHeader(sceneProps) {
return (
<NavigationHeader
{...sceneProps}
renderTitleComponent={() => (
<NavigationHeader.Title>
{sceneProps.scene.route.key}
</NavigationHeader.Title>
)}
renderLeftComponent={this._renderLeftHeader}
/>
);
}
_renderLeftHeader(sceneProps) {
if(sceneProps.scene.index > 0) {
return (
<TouchableHighlight onPress={this._onPopRoute}>
<View style={styles.backView}>
<Icon style={styles.navBarIcon} name='ios-arrow-back' size={27} />
<Text style={styles.backText}>{sceneProps.scenes[sceneProps.scene.index - 1].route.key}</Text>
</View>
</TouchableHighlight>
);
}
return (
<TouchableHighlight>
<View style={styles.backView}>
<Icon style={styles.navBarIcon} name='ios-menu' size={27} />
</View>
</TouchableHighlight>
);
}
_renderScene(sceneProps) {
return (
<MyVeryComplexScene
route={sceneProps.scene.route}
onPushRoute={this._onPushRoute}
onPopRoute={this._onPopRoute}
onExit={this.props.onExit}
/>
);
}
}
const styles = StyleSheet.create({
navigator: {
flex: 1,
},
scrollView: {
marginTop: 64
},
row: {
padding: 15,
backgroundColor: 'white',
borderBottomWidth: 1 / PixelRatio.get(),
borderBottomColor: '#CDCDCD',
},
backView: {
// flex: 2,
height: 50,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'flex-start',
},
backText: {
fontSize: 12,
textAlign: 'left',
},
backChevron: {
},
navBarIcon: {
color: '#1d1d1d',
marginLeft: 8,
marginRight: 8,
justifyContent: 'center',
},
rowText: {
fontSize: 17,
},
buttonText: {
fontSize: 17,
fontWeight: '500',
},
});
AppRegistry.registerComponent('BleedingEdgeApplication', () => BleedingEdgeApplication);
I should note that I am using React Native Vector Icons. This requires copying the Ionicon font from the font's directory into the ios project and rebuilding from within xcode.
The problem was not applying styling to <TouchableHighlight>
with a flex: 1
. This makes the component take up the entire space and let me use flex and alignItems on the <View>
that held the button and text. This made the whole styling a lot neater.
The biggest confusion was understanding that the various header components within <NavigationHeader>
are all independent, hence the seperate render option for title, left and right.
The code, as working (with colored backgrounds to show the components):
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*
* Refer: http://facebook.github.io/react-native/docs/navigation.html
*/
import React, { Component } from 'react';
import {
AppRegistry,
NavigationExperimental,
PixelRatio,
ScrollView,
StyleSheet,
View,
Text,
TouchableHighlight
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons'
const {
CardStack: NavigationCardStack,
StateUtils: NavigationStateUtils,
Header: NavigationHeader,
} = NavigationExperimental;
export default class BleedingEdgeApplication extends Component {
constructor(props, context) {
super(props, context);
this.state = {
navigationState: {
index: 0,
routes: [{key: 'Home'}]
}
}
this._onNavigationChange = this._onNavigationChange.bind(this);
}
_onNavigationChange(type) {
// extract the navigation state from the current state
let {navigationState} = this.state;
switch(type) {
case 'push':
// push a new route, which in our case is an object with a key value
const route = {key: ':' + Date.now()};
// use the push reducer provided by NavigationStateUtils
navigationState = NavigationStateUtils.push(navigationState, route);
break;
case 'pop':
// Pop the current route using the pop reducer
navigationState = NavigationStateUtils.pop(navigationState);
break;
}
if (this.state.navigationState !== navigationState) {
this.setState({navigationState});
}
}
render() {
return (
<MyVerySimpleNavigator
navigationState={this.state.navigationState}
onNavigationChange={this._onNavigationChange}
onExit={this._exit}
/>
);
}
}
class TappableRow extends Component {
render () {
return (
<TouchableHighlight
style={styles.row}
underlayColor="#D0D0D0"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>
{this.props.text}
</Text>
</TouchableHighlight>
);
}
}
class MyVeryComplexScene extends Component {
render() {
return (
<ScrollView style={styles.scrollView}>
<Text style={styles.row}>
Route: {this.props.route.key}
</Text>
<TappableRow
text="Tap me to load the next scene"
onPress={this.props.onPushRoute}
/>
<TappableRow
text="Tap me to go back"
onPress={this.props.onPopRoute}
/>
</ScrollView>
);
}
}
class MyVerySimpleNavigator extends Component {
constructor(props, context) {
super(props, context);
this._onPushRoute = this.props.onNavigationChange.bind(null, 'push');
this._onPopRoute = this.props.onNavigationChange.bind(null, 'pop');
this._onOpenSideNav = this._onOpenSideNav.bind(this);
this._renderScene = this._renderScene.bind(this);
this._renderHeader = this._renderHeader.bind(this);
this._renderLeftHeader = this._renderLeftHeader.bind(this);
}
render() {
return (
<NavigationCardStack
onNavigateBack={this._onPopRoute}
navigationState={this.props.navigationState}
renderScene={this._renderScene}
renderHeader={this._renderHeader}
style={styles.navigator}
/>
);
}
_renderHeader(sceneProps) {
return (
<NavigationHeader
{...sceneProps}
style={styles.navHeader}
renderTitleComponent={() => (
<NavigationHeader.Title style={styles.navheadertitle}>
<Text style={styles.headerTitle}>
{sceneProps.scene.route.key}
</Text>
</NavigationHeader.Title>
)}
renderLeftComponent={this._renderLeftHeader}
/>
);
}
_renderLeftHeader(sceneProps) {
if(sceneProps.scene.index > 0) {
return (
<TouchableHighlight onPress={this._onPopRoute} style={styles.headerBackTouchableHighlight}>
<View style={styles.headerBackView}>
<Icon style={styles.headerIcon} name='ios-arrow-back' size={27} />
<Text style={styles.headerBackText}>{sceneProps.scenes[sceneProps.scene.index - 1].route.key}</Text>
</View>
</TouchableHighlight>
);
}
return (
<TouchableHighlight onPress={this._onOpenSideNav} style={styles.headerBackTouchableHighlight}>
<View style={styles.headerBackView}>
<Icon style={styles.headerIcon} name='ios-menu' size={27} />
</View>
</TouchableHighlight>
);
}
_renderScene(sceneProps) {
return (
<MyVeryComplexScene
route={sceneProps.scene.route}
onPushRoute={this._onPushRoute}
onPopRoute={this._onPopRoute}
onExit={this.props.onExit}
/>
);
}
_onOpenSideNav() {
return null;
}
}
const styles = StyleSheet.create({
navigator: {
},
navHeader: {
backgroundColor: 'red',
},
navheadertitle: {
backgroundColor: '#00ff0099',
flex: 3
},
headerTitle: {
color: '#ffffff',
backgroundColor: 'blue',
},
headerBackTouchableHighlight: {
backgroundColor: 'purple',
flex: 1,
},
headerBackView: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'darkgreen',
},
headerBackText: {
fontSize: 14,
textAlign: 'left',
color: '#ffffff',
},
headerIcon: {
color: '#ffffff',
marginLeft: 8,
marginRight: 8,
justifyContent: 'center',
},
scrollView: {
marginTop: 64
},
row: {
padding: 15,
backgroundColor: 'white',
borderBottomWidth: 1 / PixelRatio.get(),
borderBottomColor: '#CDCDCD',
},
rowText: {
fontSize: 17,
},
buttonText: {
fontSize: 17,
fontWeight: '500',
},
});
AppRegistry.registerComponent('BleedingEdgeApplication', () => BleedingEdgeApplication);