A React Native library that allows you to listen to orientation changes, lock interface orientation to a selected one and get current orientation. Written in Kotlin, Swift and Typescript.
Kindly note that this library only supports the new architecture since v3.0.0, if you are looking for a version that supports the old architecture, please check older versions.
This library takes inspiration from and builds upon the following amazing alternatives:
- Get the current orientation of the device
- Get the current orientation of the interface
- Get the current interface orientation status (locked or unlocked)
- Listen to device orientation changes
- Listen to interface orientation changes
- Listen to interface orientation status changes
- Lock the interface orientation to a specific orientation
- Unlock the interface orientation
- Reset supported interface orientations to settings
- Check if autorotation is enabled (Android only)
You can install the package via npm or yarn:
npm install react-native-orientation-directoryarn add react-native-orientation-directorDon't forget to run pod-install.
You can install the package like any other Expo package, using the following command:
npx expo install react-native-orientation-directorSimply add the library plugin to your app.json file:
{
"expo": {
"plugins": [
"react-native-orientation-director"
]
}
}This way, Expo will handle the native setup for you during prebuild.
Note: only SDK 50 and above are supported, the plugin is configured to handle only the kotlin template.
This library uses a custom broadcast receiver to handle the manual orientation changes: when the user disables the autorotation feature and the system prompts the user to rotate the device, the library will listen to the broadcast sent by the MainActivity and update the interface orientation accordingly.
To allow the library to listen to the broadcast, you need to override the onConfigurationChanged method in MainActivity.kt as shown below:
// ...
import android.content.Intent
import android.content.res.Configuration
import com.orientationdirector.implementation.ConfigurationChangedBroadcastReceiver
class MainActivity : ReactActivity() {
// ...
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val orientationDirectorCustomAction =
"${packageName}.${ConfigurationChangedBroadcastReceiver.CUSTOM_INTENT_ACTION}"
val intent =
Intent(orientationDirectorCustomAction).apply {
putExtra("newConfig", newConfig)
setPackage(packageName)
}
this.sendBroadcast(intent)
}
}Nothing else is required for Android.
To properly handle interface orientation changes in iOS, you need to update your AppDelegate file. Follow the instructions below to set it up:
In your AppDelegate.swift file, implement the supportedInterfaceOrientationsFor method as follows:
import OrientationDirector
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return SharedOrientationDirectorImpl.shared.supportedInterfaceOrientations
}Starting from iOS 27, application(_:supportedInterfaceOrientationsFor:) is deprecated in favor of
UIWindowSceneDelegate.supportedInterfaceOrientations(for:). The AppDelegate method above still works, but if your
app adopts the UIScene lifecycle and you build with the iOS 27 SDK, you can implement the new method in your
SceneDelegate instead:
import OrientationDirector
func supportedInterfaceOrientations(for windowScene: UIWindowScene) -> UIInterfaceOrientationMask {
return SharedOrientationDirectorImpl.shared.supportedInterfaceOrientations
}If you need help, you can check the example project.
This library exports a class called: RNOrientationDirector that exposes the following methods:
| Method | Description |
|---|---|
| getInterfaceOrientation | Returns the last interface orientation |
| getDeviceOrientation | Returns the last device orientation |
| lockTo | Locks the interface to a specific orientation |
| unlock | Unlock the interface |
| isLocked | Returns the current interface orientation status (locked / unlocked) |
| isAutoRotationEnabled | (Android Only) Returns if auto rotation is enabled |
| listenForDeviceOrientationChanges | Triggers a provided callback each time the device orientation changes |
| listenForInterfaceOrientationChanges | Triggers a provided callback each time the interface orientation changes |
| listenForLockChanges | Triggers a provided callback each time the interface orientation status changes |
| convertOrientationToHumanReadableString | Returns a human readable string based on the given orientation |
| convertAutoRotationToHumanReadableString | Returns a human readable string based on the given auto rotation |
| setHumanReadableOrientations | Sets the mapping needed to convert orientation values to human readable strings |
| setHumanReadableAutoRotations | Sets the mapping needed to convert auto rotation values to human readable strings |
| resetSupportedInterfaceOrientations | Resets the supported interface orientations to settings |
| isLockableOrientation | Determines if orientation is lockable |
In addition, the library exposes the following hooks:
| Hook | Description |
|---|---|
| useInterfaceOrientation | Returns the current interface orientation and listens to changes |
| useDeviceOrientation | Returns the current device orientation and listens to changes |
| useIsInterfaceOrientationLocked | Returns the current interface orientation status and listens to changes |
Head over to the example project to see how to use the library.
Please be aware that there is a subtle difference between the device orientation and the interface orientation.
When you device is either in landscape left or right orientation, your interface is inverted, this is why lockTo method needs a second parameter to discriminate which type of orientation your are supplying.
To match developers expectations, if you supply a device orientation and OrientationType.device, lockTo switches landscapeRight with left and vice versa to property align the interface orientation.
This behavior comes from the native API, you can find more information in their documentation:
Starting from iOS 16, the interface orientation is read from the window scene geometry, which reflects what the
system actually displays. As a consequence, after calling lockTo or resetSupportedInterfaceOrientations the new
interface orientation is delivered through the listeners (and hooks) as soon as the system applies it, instead of
being assumed right away.
On foldable devices the app moves between the outer and the inner display when the device is folded or unfolded:
- The interface orientation is updated on fold / unfold, even though the device itself is not rotated;
- The device orientation is the one reported by iOS (
UIDevice.orientation), which is relative to the device body and does not change on fold / unfold. The iPhone Duo inner display is mounted rotated by 90° relative to the body, so an unfolded device can report aportraitdevice orientation together with a landscape interface orientation, exactly like a native app would read them; - The inner display is a resizable environment: iOS treats the supported interface orientations as a preference and
ignores them there. This means that
lockTohas no effect on the inner display, while it keeps working on the outer one.isLockedstill reports whether a lock has been requested, and the interface orientation always reports the orientation actually displayed.
Since on Android we need to deal with sensors and their usage, it is worth noting that the device orientation computation works differently than on iOS, mainly in the following ways:
- Upon start up, all required sensors are enabled just for the initial device orientation computation, then they are disabled;
- Each time a new device orientation listener is added, all required sensors are enabled if disabled;
- After the last device orientation listener is removed, all required sensors are disabled;
This behavior allows us to follow Google's best practices related to the Sensors Framework. More here.
See the contributing guide to learn how to contribute to the repository and the development workflow.
Made with create-react-native-library