How can I disable rotation only for specific views (e.g: when using Navigator) and not for the entire app?
The question here already addresses disabling rotation for the entire app
With the react-native-orientation package, it's possible to lock the orientation to portrait/landscape. The package and documentation can be found here: https://github.com/yamill/react-native-orientation
Remember; you should not put your locking inside the rendering of scenes (nor the renderScene method). Since the Navigator re-renders all the scenes in the route stack, this would probably cause weird side effects for you. Rather, the locking/unlocking should be put in the code that interacts with the route stack (ie. calls the push/pop methods).
If your case is about more specific control over orientations of different screens in StackNavigator (something like Portrait -> LandscapeLeft -> LandscapeRight -> Portrait, and all the way back), here is a may-not-that-pretty solution:
packages needed: react-navigation, react-native-orientation;
define base screens as follow:
// baseScreen.js
import React, { Component } from "react";
import Orientation from "react-native-orientation";
export class PortraitScreen extends Component {
constructor(props) {
super(props);
this._willFocusSubscription = this.props.navigation.addListener("willFocus", payload => {
// lock to portrait when this screen is about to appear
Orientation.lockToPortrait();
})
}
componentWillUnmount() {
// remove subscription when unmount
this._willFocusSubscription.remove();
}
}
export class LandscapeScreen extends Component {
constructor(props) {
super(props);
this._willFocusSubscription = this.props.navigation.addListener("willFocus", payload => {
// lock to landscape
Orientation.lockToLandscape();
})
}
componentWillUnmount() {
// remove subscription either
this._willFocusSubscription.remove();
}
}
define concrete screens which extends abovementioned base screen(s):
// moduleScreens.js
import React from "react";
import { Button, View } from "react-native";
import { PortraitScreen, LandscapeScreen } from "/path/to/baseScreen";
export class VideoDescScreen extends PortraitScreen {
render() {
return (
<View>
<Button
title="watch video"
onPress={() => this.props.navigation.navigate("VideoPlayer")}
/>
</View>
)
}
}
export class VideoPlayerScreen extends LandscapeScreen {
render() {
return <View>...</View>
}
}
create route like this:
// route.js
import React from "react";
import { createStackNavigator } from "react-navigation";
import { VideoDescScreen, VideoPlayerScreen } from "/path/to/moduleScreens";
const stack = createStackNavigator(
{
VideoDesc: {
screen: VideoDescScreen
},
VideoPlayer: {
screen: VideoPlayerScreen
}
}
)
How it works? According to doc, we observe event willFocus when screen is initialized, and each time this screen is about to appear (focused) in navigation, we lock device to our desired orientation, works for both PUSH(to) and POP(back from) behaviors.
Hope it helps.
Related
Why is this.props.componentId needed?
What is its purpose?
Why can't we use the library without that id being needed?
react-navigation doesn't need something like that, and react-native-navigation v1 didn't use anything like that. So why does v2 needs and uses that? The reason I ask is firstly to understand it, and secondly to see if I can skip this since now I cannot use RNN v2 from a saga.
Here's a detailed answer from a blogpost by the react-native-navigation library developer.
So now we want to enable the following behavior: after user clicks on the text, the app pushes the ViewPost screen. Later on it will be very easy to attach the same function to a list item instead of the text. To push a new screen into this screen’s navigation stack, we will use Navigation.push. In the new API this method expects to receive the current componentId which can be found in props.componentID. So in PostsList.js we create a pushViewPostScreen function and attach it to the onPress event of the Text.
import React, {PureComponent} from 'react';
import {View, Text} from 'react-native-ui-lib';
import PropTypes from 'prop-types';
import {Navigation} from 'react-native-navigation';
class PostsList extends PureComponent {
static propTypes = {
navigator: PropTypes.object,
componentId: PropTypes.string
};
constructor(props) {
super(props);
this.pushViewPostScreen = this.pushViewPostScreen.bind(this);
}
pushViewPostScreen() {
// We pass the componentId to Navigation.push
// to reference the which component will be pushed
// to the navigation stack
Navigation.push(this.props.componentId, {
component: {
name: 'blog.ViewPost',
passProps: {
text: 'Some props that we are passing'
},
options: {
topBar: {
title: {
text: 'Post1'
}
}
}
}
});
}
render() {
return (
<View flex center bg-blue60>
<Text onPress={this.pushViewPostScreen}>Posts List Screen</Text>
</View>
);
}
}
export default PostsList;
In the official docs, it seems that the Screen API, which is responsible of pushing, popping and changing navigation, will be accessible through the Navigation module always expecting a props.componentId to get the reference to the component.
Is there a way to read the options before using the mergeOptions function.
I'm trying to add a sideMenu that opens and closes with the same button. But to handle that logic, Instead of making use of redux, I want to read the options before the merge, so I can simply do something like visible: !pastVisible.
navigationButtonPressed({ buttonId }) {
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
'left': {
visible: false
}
}
});
console.log(`Se presiono ${buttonId}`);
}
So basically I want to read the value of the visible option before changed it.
By now, I can only achieve this using redux.
import React, {Component} from 'react';
import {View, Text} from 'react-native';
import { Navigation } from 'react-native-navigation';
import { connect } from 'react-redux';
import { toggleSideMenu } from './../../store/actions/index';
class SideDrawer extends Component {
constructor(props) {
super(props);
Navigation.events().registerComponentDidDisappearListener(({ componentId }) => {
this.props.toggleSideMenu(false);
});
}
render() {
return (
<View>
<Text>This is the sidedrawer</Text>
</View>
);
}
}
const mapDispatchToProps = dispatch => {
return {
toggleSideMenu: (visible) => dispatch(toggleSideMenu(visible))
};
};
export default connect(null, mapDispatchToProps)(SideDrawer);
Then I just add the listeners to the sidemenu component. Depending on the case, I update the current state of the component (visible or not).
Finally on the components where I want to use the side drawer button I just implement the navigationButtenPressed method. Then I just call the reducer to know the current visible state and toggled it.
navigationButtonPressed({ buttonId }) {
const visible = !this.props.sideMenu;
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
'left': {
visible: visible
}
}
});
this.props.toggleSideMenu(visible);
}
If there is a more easy way to achieve this I'll be glad to know about it.
I used TabNavigation for my app. In some screen, I want it only display on portrait orientation, and others are landscape. I've found react-native-orientation and try in my screens:
Screen 1
import React from "react";
import Orientation from 'react-native-orientation';
import { Text } from 'react-native'
export default Screen1 extends React.PureComponent{
componentDidMount(){
Orientation.lockToLandscape();
}
componentWillUnmount(){
Orientation.unlockAllOrientations();
}
render() {
return(<Text>Screen 1</Text>);
}
}
Screen 2
import React from "react";
import Orientation from 'react-native-orientation';
import { Text } from 'react-native'
export default Screen2 extends React.PureComponent{
componentDidMount(){
Orientation.lockToPortrait();
}
componentWillUnmount(){
Orientation.unlockAllOrientations();
}
render() {
return(<Text>Screen 2</Text>);
}
}
Tab Navigator
const AppNavigator = TabNavigator( {
Screen1: { screen: Screen1 },
Screen2: { screen: Screen2 },
});
But it always portrait, which mean that its orientation always set base on orientation of last screen I add in TabNavigator. Any help is appreciate, thank you in advance!
Edit 1
I've try StackNavigation instead, and it work. Still don't know why it's not run with TabNavigation.
It has been long time since I asked this questions, and react-navigation is upgrade to version 3. My solution for this problem is set orientation for tab each time it's pressed, cause tab screen isn't unmount when you click on other tab. To fix that, you can add tabBarOnPress to your navigationOptions of your screen like this
import Orientation from 'react-native-orientation';
...
class TabbarScreen1 extends Component {
static navigationOptions = {
...,
tabBarOnPress: ({ defaultHandler }) => {
// Orientation.lockToLandscape();
Orientation.lockToPortrait();
defaultHandler();
},
}
...
}
Im trying to implement Backhandler with Listener, In componentWillMount im adding the listener and in componentWillUnMount im removing the listener but componentWillUnMount is not called when we push to other component. So Listener is there in other Components also, Is there a problem of react-native-router-flux with tabbar
To make back handler work with a centralised configuration, I'd normally have the handler in a component called AppNavigation which is the parent for Router component.
It looks something like:
<AppNavigation>
<Router>
<Scene key="root">
{/* other scenes */}
</Scene>
</Router>
</AppNavigation>
Handling the back button in AppNavigation would then be relatively straightforward:
import React, {Component} from "react";
import {BackAndroid} from "react-native";
import {Actions} from "react-native-router-flux";
class AppNavigation extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
//listens to hardwareBackPress
BackAndroid.addEventListener('hardwareBackPress', () => {
try {
Actions.pop();
return true;
}
catch (err) {
console.debug("Can't pop. Exiting the app...");
return false;
}
});
}
componentWillUnmount(){
console.log("Unmounting app, removing listeners");
BackAndroid.removeEventListener('hardwareBackPress');
}
render() {
return this.props.children;
}
}
export default AppNavigation;
P.S. Don't forget to differentiate between android and iOS though, since I believe iOS does not have a back button.
I am new in react-native. I have problems wiht implementing orientation for ios. I am trying to use react native orientation. I have read the documentation but can't do it anyway. Please don't answer with methods like "lockToPortrait(),lockToLandscape() or getOrientation(function(err, orientation)", I have already read this. Can anyone just respond with a piece of working code, it will be enough for me.
Thanks
var Orientation = require('react-native-orientation');
class YourApp extends Component {
componentDidMount() {
Orientation.lockToPortrait();
}
render() {
<MainComponent />
}
}
AppRegistry.registerComponent('YourApp', () => YourApp);
Worked for me just fine.
import React, {PureComponent} from 'react';
import {
View,
Text,
Dimensions
} from 'react-native';
import Orientation from 'react-native-orientation';
const {
width: deviceScreenWidth,
height: deviceScreenHeight
} = Dimensions.get('window');
let aspectRatio = deviceScreenWidth / deviceScreenHeight;
export default class Home extends PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<View style={style.homeMainContainer}>
<Text>Atif dont let this screen rotate</Text>
</View>
)
}// end of render
componentDidMount() {
if (deviceScreenWidth > deviceScreenHeight) {
Orientation.lockToLandscape();
} else {
Orientation.lockToPortrait();
}
// this unlocks any previous locks to all Orientations
// Orientation.unlockAllOrientations();
Orientation.addOrientationListener(this._orientationDidChange);
}
_orientationDidChange = (orientation) => {
if (orientation === 'LANDSCAPE') {
// do something with landscape layout
} else {
// do something with portrait layout
}
}
};
This example is to figure out the the device is the tablet or mobile, if it is tablet it will lock the screen as landscape and if it is mobile then it will lock the screen as portrait.
This is the library as package first you have to install the package link : https://www.npmjs.com/package/react-native-orientation
Adding this since none of the above mentioned that this can be done in react native, without importing a library with Dimensions.addEventListener.
Here is an example of a class that can be initiated to remove and add the StatusBar in your app.
import { Dimensions } from "react-native";
class OrientationChanger {
constructor() {
this.update();
Dimensions.addEventListener("change", () => {
this.updateOrientation();
});
}
update() {
if (Dimensions.get("window").width < Dimensions.get("window").height) {
StatusBar.setHidden(false); // "portrait"
} else {
StatusBar.setHidden(true); // "landscape"
}
}
}
export default OrientationChanger;