expo-screen-orientation 5.2.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +8 -1
- package/android/build.gradle +8 -10
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/expo/modules/screenorientation/ScreenOrientationModule.kt +7 -3
- package/build/ExpoScreenOrientation.d.ts.map +1 -1
- package/build/ExpoScreenOrientation.js +2 -5
- package/build/ExpoScreenOrientation.js.map +1 -1
- package/expo-module.config.json +2 -1
- package/ios/{EXScreenOrientation.podspec → ExpoScreenOrientation.podspec} +9 -2
- package/ios/{EXScreenOrientation/ScreenOrientationAppDelegate.swift → ScreenOrientationAppDelegate.swift} +5 -3
- package/ios/ScreenOrientationExceptions.swift +16 -0
- package/ios/ScreenOrientationModule.swift +118 -0
- package/ios/ScreenOrientationRNSScreenWindowTraits.h +11 -0
- package/ios/{EXScreenOrientation/ScreenOrientationReactDelegateHandler.swift → ScreenOrientationReactDelegateHandler.swift} +1 -1
- package/ios/ScreenOrientationRegistry.swift +252 -0
- package/ios/ScreenOrientationUtilities.swift +110 -0
- package/ios/ScreenOrientationViewController.swift +64 -0
- package/ios/enums/ModuleOrientation.swift +39 -0
- package/ios/enums/ModuleOrientationLock.swift +60 -0
- package/package.json +2 -2
- package/src/ExpoScreenOrientation.ts +2 -5
- package/ios/EXScreenOrientation/EXScreenOrientationModule.h +0 -11
- package/ios/EXScreenOrientation/EXScreenOrientationModule.m +0 -177
- package/ios/EXScreenOrientation/EXScreenOrientationRegistry.h +0 -46
- package/ios/EXScreenOrientation/EXScreenOrientationRegistry.m +0 -272
- package/ios/EXScreenOrientation/EXScreenOrientationUtilities.h +0 -25
- package/ios/EXScreenOrientation/EXScreenOrientationUtilities.m +0 -172
- package/ios/EXScreenOrientation/EXScreenOrientationViewController.h +0 -14
- package/ios/EXScreenOrientation/EXScreenOrientationViewController.m +0 -88
- package/ios/EXScreenOrientation/NSString+UIInterfaceOrientationMask.h +0 -9
- package/ios/EXScreenOrientation/NSString+UIInterfaceOrientationMask.m +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,27 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 6.0.0 — 2023-06-21
|
|
14
|
+
|
|
15
|
+
_This version does not introduce any user-facing changes._
|
|
16
|
+
|
|
17
|
+
## 6.0.0-beta.1 — 2023-06-13
|
|
18
|
+
|
|
19
|
+
### 🎉 New features
|
|
20
|
+
|
|
21
|
+
- Added support for React Native 0.72. ([#22588](https://github.com/expo/expo/pull/22588) by [@kudo](https://github.com/kudo))
|
|
22
|
+
|
|
23
|
+
### 🐛 Bug fixes
|
|
24
|
+
|
|
25
|
+
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
|
|
26
|
+
- [iOS] Fixed screen orientation on iOS 16. ([#22152](https://github.com/expo/expo/pull/22152) by [@behenate](https://github.com/behenate))
|
|
27
|
+
- [iOS] Fixed status bar and navigation bar following the device's orientation regardless of applied orientation lock. ([#22152](https://github.com/expo/expo/pull/22152) by [@behenate](https://github.com/behenate))
|
|
28
|
+
- [iOS] Fixed SafeAreaViews failing after pulling down quick settings when the device is in a different orientation than the current orientation lock allows. ([#22152](https://github.com/expo/expo/pull/22152) by [@behenate](https://github.com/behenate))
|
|
29
|
+
|
|
30
|
+
### 💡 Others
|
|
31
|
+
|
|
32
|
+
- [iOS] Migrated to new modules API. ([#22152](https://github.com/expo/expo/pull/22152) by [@behenate](https://github.com/behenate))
|
|
33
|
+
|
|
13
34
|
## 5.2.0 — 2023-05-08
|
|
14
35
|
|
|
15
36
|
### 🎉 New features
|
package/README.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
<p>
|
|
2
|
+
<a href="https://docs.expo.dev/versions/latest/sdk/screen-orientation/">
|
|
3
|
+
<img
|
|
4
|
+
src="../../.github/resources/expo-screen-orientation.svg"
|
|
5
|
+
alt="expo-screen-orientation"
|
|
6
|
+
height="64" />
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
2
9
|
|
|
3
10
|
Allows you to manage the orientation of your app's interface.
|
|
4
11
|
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '
|
|
6
|
+
version = '6.0.0'
|
|
7
7
|
|
|
8
8
|
buildscript {
|
|
9
9
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
@@ -35,19 +35,11 @@ buildscript {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Creating sources with comments
|
|
39
|
-
task androidSourcesJar(type: Jar) {
|
|
40
|
-
classifier = 'sources'
|
|
41
|
-
from android.sourceSets.main.java.srcDirs
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
afterEvaluate {
|
|
45
39
|
publishing {
|
|
46
40
|
publications {
|
|
47
41
|
release(MavenPublication) {
|
|
48
42
|
from components.release
|
|
49
|
-
// Add additional sourcesJar to artifacts
|
|
50
|
-
artifact(androidSourcesJar)
|
|
51
43
|
}
|
|
52
44
|
}
|
|
53
45
|
repositories {
|
|
@@ -70,15 +62,21 @@ android {
|
|
|
70
62
|
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
|
71
63
|
}
|
|
72
64
|
|
|
65
|
+
namespace "expo.modules.screenorientation"
|
|
73
66
|
defaultConfig {
|
|
74
67
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
75
68
|
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
76
69
|
versionCode 7
|
|
77
|
-
versionName '
|
|
70
|
+
versionName '6.0.0'
|
|
78
71
|
}
|
|
79
72
|
lintOptions {
|
|
80
73
|
abortOnError false
|
|
81
74
|
}
|
|
75
|
+
publishing {
|
|
76
|
+
singleVariant("release") {
|
|
77
|
+
withSourcesJar()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
dependencies {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<manifest
|
|
1
|
+
<manifest>
|
|
2
2
|
</manifest>
|
|
@@ -17,8 +17,12 @@ import expo.modules.screenorientation.enums.OrientationAttr
|
|
|
17
17
|
import expo.modules.screenorientation.enums.OrientationLock
|
|
18
18
|
|
|
19
19
|
class ScreenOrientationModule : Module(), LifecycleEventListener {
|
|
20
|
+
private val weakCurrentActivity
|
|
21
|
+
get() = appContext.activityProvider?.currentActivity
|
|
22
|
+
|
|
20
23
|
private val currentActivity
|
|
21
|
-
get() =
|
|
24
|
+
get() = weakCurrentActivity ?: throw Exceptions.MissingActivity()
|
|
25
|
+
|
|
22
26
|
private val uiManager
|
|
23
27
|
get() = appContext.legacyModuleRegistry.getModule(UIManager::class.java)
|
|
24
28
|
?: throw IllegalStateException("Could not find implementation for UIManager.")
|
|
@@ -71,13 +75,13 @@ class ScreenOrientationModule : Module(), LifecycleEventListener {
|
|
|
71
75
|
OnDestroy {
|
|
72
76
|
uiManager.unregisterLifecycleEventListener(this@ScreenOrientationModule)
|
|
73
77
|
initialOrientation?.let {
|
|
74
|
-
|
|
78
|
+
weakCurrentActivity?.requestedOrientation = it
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
override fun onHostResume() {
|
|
80
|
-
initialOrientation = initialOrientation ?:
|
|
84
|
+
initialOrientation = initialOrientation ?: weakCurrentActivity?.requestedOrientation
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
override fun onHostPause() = Unit
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoScreenOrientation.d.ts","sourceRoot":"","sources":["../src/ExpoScreenOrientation.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ExpoScreenOrientation.d.ts","sourceRoot":"","sources":["../src/ExpoScreenOrientation.ts"],"names":[],"mappings":";AAEA,wBAA4D"}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export default Platform.OS === 'android'
|
|
4
|
-
? requireNativeModule('ExpoScreenOrientation')
|
|
5
|
-
: NativeModulesProxy.ExpoScreenOrientation || {};
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
export default requireNativeModule('ExpoScreenOrientation');
|
|
6
3
|
//# sourceMappingURL=ExpoScreenOrientation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoScreenOrientation.js","sourceRoot":"","sources":["../src/ExpoScreenOrientation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ExpoScreenOrientation.js","sourceRoot":"","sources":["../src/ExpoScreenOrientation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,eAAe,mBAAmB,CAAC,uBAAuB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nexport default requireNativeModule('ExpoScreenOrientation');\n"]}
|
package/expo-module.config.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"ios": {
|
|
8
8
|
"appDelegateSubscribers": ["ScreenOrientationAppDelegate"],
|
|
9
|
-
"reactDelegateHandlers": ["ScreenOrientationReactDelegateHandler"]
|
|
9
|
+
"reactDelegateHandlers": ["ScreenOrientationReactDelegateHandler"],
|
|
10
|
+
"modules": ["ScreenOrientationModule"]
|
|
10
11
|
}
|
|
11
12
|
}
|
|
@@ -3,7 +3,7 @@ require 'json'
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name = '
|
|
6
|
+
s.name = 'ExpoScreenOrientation'
|
|
7
7
|
s.version = package['version']
|
|
8
8
|
s.summary = package['description']
|
|
9
9
|
s.description = package['description']
|
|
@@ -18,6 +18,13 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
s.dependency 'ExpoModulesCore'
|
|
19
19
|
s.dependency 'React-Core'
|
|
20
20
|
|
|
21
|
+
unless defined?(install_modules_dependencies)
|
|
22
|
+
# `install_modules_dependencies` is defined from react_native_pods.rb.
|
|
23
|
+
# when running with `pod ipc spec`, this method is not defined and we have to require manually.
|
|
24
|
+
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
|
25
|
+
end
|
|
26
|
+
install_modules_dependencies(s)
|
|
27
|
+
|
|
21
28
|
# Swift/Objective-C compatibility
|
|
22
29
|
s.pod_target_xcconfig = {
|
|
23
30
|
'DEFINES_MODULE' => 'YES'
|
|
@@ -27,6 +34,6 @@ Pod::Spec.new do |s|
|
|
|
27
34
|
s.source_files = "#{s.name}/**/*.h"
|
|
28
35
|
s.vendored_frameworks = "#{s.name}.xcframework"
|
|
29
36
|
else
|
|
30
|
-
s.source_files = "
|
|
37
|
+
s.source_files = "**/*.{h,m,swift}"
|
|
31
38
|
end
|
|
32
39
|
end
|
|
@@ -4,9 +4,11 @@ import ExpoModulesCore
|
|
|
4
4
|
|
|
5
5
|
public class ScreenOrientationAppDelegate: ExpoAppDelegateSubscriber {
|
|
6
6
|
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
|
7
|
-
|
|
8
|
-
screenOrientationRegistry.updateCurrentScreenOrientation()
|
|
9
|
-
}
|
|
7
|
+
ScreenOrientationRegistry.shared.updateCurrentScreenOrientation()
|
|
10
8
|
return true
|
|
11
9
|
}
|
|
10
|
+
|
|
11
|
+
public func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
|
12
|
+
return ScreenOrientationRegistry.shared.currentOrientationMask
|
|
13
|
+
}
|
|
12
14
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
internal class InvalidOrientationLockException: Exception {
|
|
4
|
+
override var reason: String {
|
|
5
|
+
"Invalid screen orientation lock"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
internal class UnsupportedOrientationLockException: GenericException<ModuleOrientationLock?> {
|
|
10
|
+
override var reason: String {
|
|
11
|
+
guard let param = param else {
|
|
12
|
+
return "This device does not support the requested orientation"
|
|
13
|
+
}
|
|
14
|
+
return "This device does not support the requested orientation: \(param)"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
public class ScreenOrientationModule: Module, OrientationListener, Hashable {
|
|
4
|
+
static let didUpdateDimensionsEvent = "expoDidUpdateDimensions"
|
|
5
|
+
|
|
6
|
+
let screenOrientationRegistry = ScreenOrientationRegistry.shared
|
|
7
|
+
var eventEmitter: EXEventEmitterService?
|
|
8
|
+
|
|
9
|
+
public func definition() -> ModuleDefinition {
|
|
10
|
+
Name("ExpoScreenOrientation")
|
|
11
|
+
|
|
12
|
+
Events("expoDidUpdateDimensions")
|
|
13
|
+
|
|
14
|
+
AsyncFunction("lockAsync") { (orientationLock: ModuleOrientationLock) in
|
|
15
|
+
let orientationMask = orientationLock.toInterfaceOrientationMask()
|
|
16
|
+
|
|
17
|
+
guard !orientationMask.isEmpty else {
|
|
18
|
+
throw InvalidOrientationLockException()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
guard orientationMask.isSupportedByDevice() else {
|
|
22
|
+
throw UnsupportedOrientationLockException(orientationLock)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
screenOrientationRegistry.setMask(orientationMask, forModule: self)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
AsyncFunction("lockPlatformAsync") { (allowedOrientations: [ModuleOrientation]) in
|
|
29
|
+
var allowedOrientationsMask: UIInterfaceOrientationMask = []
|
|
30
|
+
|
|
31
|
+
for allowedOrientation in allowedOrientations {
|
|
32
|
+
let orientation = allowedOrientation.toInterfaceOrientation()
|
|
33
|
+
let orientationMask = orientation.toInterfaceOrientationMask()
|
|
34
|
+
|
|
35
|
+
guard !orientationMask.isEmpty else {
|
|
36
|
+
throw InvalidOrientationLockException()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
allowedOrientationsMask.insert(orientationMask)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
guard allowedOrientationsMask.isSupportedByDevice() else {
|
|
43
|
+
throw UnsupportedOrientationLockException(nil)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
screenOrientationRegistry.setMask(allowedOrientationsMask, forModule: self)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
AsyncFunction("getOrientationLockAsync") {
|
|
50
|
+
return ModuleOrientationLock.from(mask: screenOrientationRegistry.currentOrientationMask).rawValue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
AsyncFunction("getPlatformOrientationLockAsync") { () -> [Int] in
|
|
54
|
+
let orientationMask = screenOrientationRegistry.currentOrientationMask
|
|
55
|
+
var allowedOrientations: [Int] = []
|
|
56
|
+
let orientationMasks: [UIInterfaceOrientationMask] = [.portrait, .portraitUpsideDown, .landscapeLeft, .landscapeRight]
|
|
57
|
+
|
|
58
|
+
// If the particular orientation is supported, we add it to the array of allowedOrientations
|
|
59
|
+
for wrappedSingleOrientation in orientationMasks {
|
|
60
|
+
let supportedOrientationMask = orientationMask.intersection(wrappedSingleOrientation)
|
|
61
|
+
if !supportedOrientationMask.isEmpty {
|
|
62
|
+
let supportedOrientation = supportedOrientationMask.toUIInterfaceOrientation()
|
|
63
|
+
allowedOrientations.append(ModuleOrientation.from(orientation: supportedOrientation).rawValue)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return allowedOrientations
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
AsyncFunction("supportsOrientationLockAsync") { (orientationLock: ModuleOrientationLock) -> Bool in
|
|
70
|
+
let orientationMask = orientationLock.toInterfaceOrientationMask()
|
|
71
|
+
return !orientationMask.isEmpty && orientationMask.isSupportedByDevice()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
AsyncFunction("getOrientationAsync") {
|
|
75
|
+
return ModuleOrientation.from(orientation: screenOrientationRegistry.currentScreenOrientation).rawValue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
OnStartObserving {
|
|
79
|
+
screenOrientationRegistry.registerModuleToReceiveNotification(self)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
OnStopObserving {
|
|
83
|
+
screenOrientationRegistry.unregisterModuleFromReceivingNotification(self)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
OnDestroy {
|
|
87
|
+
screenOrientationRegistry.unregisterModuleFromReceivingNotification(self)
|
|
88
|
+
screenOrientationRegistry.moduleWillDeallocate(self)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// MARK: - ScreenOrientationListener
|
|
93
|
+
|
|
94
|
+
func screenOrientationDidChange(_ orientation: UIInterfaceOrientation) {
|
|
95
|
+
guard let currentTraitCollection = screenOrientationRegistry.currentTraitCollection else {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
sendEvent(ScreenOrientationModule.didUpdateDimensionsEvent, [
|
|
100
|
+
"orientationLock": ModuleOrientationLock.from(mask: screenOrientationRegistry.currentOrientationMask).rawValue,
|
|
101
|
+
"orientationInfo": [
|
|
102
|
+
"orientation": ModuleOrientation.from(orientation: orientation).rawValue,
|
|
103
|
+
"verticalSizeClass": currentTraitCollection.verticalSizeClass,
|
|
104
|
+
"horizontalSizeClass": currentTraitCollection.horizontalSizeClass
|
|
105
|
+
] as [String: Any]
|
|
106
|
+
])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// MARK: - Hashable
|
|
110
|
+
|
|
111
|
+
public func hash(into hasher: inout Hasher) {
|
|
112
|
+
hasher.combine(ObjectIdentifier(self))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public static func == (lhs: ScreenOrientationModule, rhs: ScreenOrientationModule) -> Bool {
|
|
116
|
+
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
This is necessary for the shouldUseRNSScreensOrientation function in ScreenOrientationViewController
|
|
5
|
+
This is a copy of the RNSScreens Protocol
|
|
6
|
+
*/
|
|
7
|
+
@protocol ScreenOrientationRNSScreenWindowTraits
|
|
8
|
+
|
|
9
|
+
+ (BOOL)shouldAskScreensForScreenOrientationInViewController:(UIViewController *)vc;
|
|
10
|
+
|
|
11
|
+
@end
|
|
@@ -4,6 +4,6 @@ import ExpoModulesCore
|
|
|
4
4
|
|
|
5
5
|
public class ScreenOrientationReactDelegateHandler: ExpoReactDelegateHandler {
|
|
6
6
|
public override func createRootViewController(reactDelegate: ExpoReactDelegate) -> UIViewController? {
|
|
7
|
-
return
|
|
7
|
+
return ScreenOrientationViewController(defaultScreenOrientationFromPlist: ())
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import ExpoModulesCore
|
|
3
|
+
|
|
4
|
+
protocol OrientationListener {
|
|
5
|
+
func screenOrientationDidChange(_ orientation: UIInterfaceOrientation)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
This singleton that holds information about desired orientation for every app which uses expo-screen-orientation.
|
|
10
|
+
Marked @objc and public, because this it is also used in EXAppViewController.
|
|
11
|
+
*/
|
|
12
|
+
@objc
|
|
13
|
+
public class ScreenOrientationRegistry: NSObject, UIApplicationDelegate {
|
|
14
|
+
@objc
|
|
15
|
+
public static let shared = ScreenOrientationRegistry()
|
|
16
|
+
|
|
17
|
+
var currentScreenOrientation: UIInterfaceOrientation
|
|
18
|
+
var orientationListeners: [ScreenOrientationModule?] = []
|
|
19
|
+
var moduleInterfaceMasks: [ScreenOrientationModule: UIInterfaceOrientationMask] = [:]
|
|
20
|
+
weak var currentTraitCollection: UITraitCollection?
|
|
21
|
+
var lastOrientationMask: UIInterfaceOrientationMask
|
|
22
|
+
var rootViewController: UIViewController? {
|
|
23
|
+
return UIApplication.shared.keyWindow?.rootViewController
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var currentOrientationMask: UIInterfaceOrientationMask {
|
|
27
|
+
var currentOrientationMask: UIInterfaceOrientationMask = []
|
|
28
|
+
|
|
29
|
+
EXUtilities.performSynchronously {
|
|
30
|
+
currentOrientationMask = self.rootViewController?.supportedInterfaceOrientations ?? []
|
|
31
|
+
}
|
|
32
|
+
return currentOrientationMask
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private override init() {
|
|
36
|
+
self.currentScreenOrientation = .unknown
|
|
37
|
+
self.currentTraitCollection = nil
|
|
38
|
+
self.lastOrientationMask = []
|
|
39
|
+
|
|
40
|
+
super.init()
|
|
41
|
+
|
|
42
|
+
NotificationCenter.default.addObserver(
|
|
43
|
+
self,
|
|
44
|
+
selector: #selector(self.handleDeviceOrientationChange(notification:)),
|
|
45
|
+
name: UIDevice.orientationDidChangeNotification,
|
|
46
|
+
object: UIDevice.current
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// This is most likely already executed on the main thread, but we need to be sure
|
|
50
|
+
RCTExecuteOnMainQueue {
|
|
51
|
+
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
Called by ScreenOrientationAppDelegate in order to set initial interface orientation.
|
|
57
|
+
*/
|
|
58
|
+
func updateCurrentScreenOrientation() {
|
|
59
|
+
let windows = UIApplication.shared.windows
|
|
60
|
+
if !windows.isEmpty {
|
|
61
|
+
self.currentScreenOrientation = windows[0].windowScene?.interfaceOrientation ?? .unknown
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
deinit {
|
|
66
|
+
EXUtilities.performSynchronously {
|
|
67
|
+
UIDevice.current.endGeneratingDeviceOrientationNotifications()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// MARK: - Affecting screen orientation
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
Rotates the view to currentScreenOrientation or default orientation from the orientationMask.
|
|
75
|
+
*/
|
|
76
|
+
func enforceDesiredDeviceOrientation(withOrientationMask orientationMask: UIInterfaceOrientationMask) {
|
|
77
|
+
var newOrientation = orientationMask.defaultOrientation()
|
|
78
|
+
|
|
79
|
+
if orientationMask.contains(currentScreenOrientation) {
|
|
80
|
+
newOrientation = currentScreenOrientation
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
guard newOrientation != .unknown else {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
RCTExecuteOnMainQueue { [weak self] in
|
|
88
|
+
guard let self = self else {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if #available(iOS 16.0, *) {
|
|
93
|
+
let windowScene = self.rootViewController?.view.window?.windowScene
|
|
94
|
+
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientationMask))
|
|
95
|
+
self.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
|
|
96
|
+
} else {
|
|
97
|
+
UIDevice.current.setValue(newOrientation.rawValue, forKey: "orientation")
|
|
98
|
+
UIViewController.attemptRotationToDeviceOrientation()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if self.currentScreenOrientation == .unknown {
|
|
102
|
+
// CurrentScreenOrientation might be unknown (especially just after launch), but at this point we already know it.
|
|
103
|
+
// Later the currentScreenOrientation will be updated by the iOS orientation change notifications.
|
|
104
|
+
self.screenOrientationDidChange(newOrientation)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func setMask(_ mask: UIInterfaceOrientationMask, forModule module: ScreenOrientationModule) {
|
|
110
|
+
moduleInterfaceMasks[module] = mask
|
|
111
|
+
enforceDesiredDeviceOrientation(withOrientationMask: mask)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - Getters
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
Gets the orientationMask for the app. Uses an intersection of all applied orientation masks. Also used for Expo Go in EXAppViewController.
|
|
118
|
+
*/
|
|
119
|
+
@objc
|
|
120
|
+
public func requiredOrientationMask() -> UIInterfaceOrientationMask {
|
|
121
|
+
if moduleInterfaceMasks.isEmpty {
|
|
122
|
+
return []
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// We want to apply an orientation mask which is an intersection of locks applied by the modules.
|
|
126
|
+
var mask = doesDeviceHaveNotch ? UIInterfaceOrientationMask.allButUpsideDown : UIInterfaceOrientationMask.all
|
|
127
|
+
|
|
128
|
+
for moduleMask in moduleInterfaceMasks {
|
|
129
|
+
mask = mask.intersection(moduleMask.value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return mask
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// MARK: - Events
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
Called when the OS sends an OrientationDidChange notification.
|
|
139
|
+
*/
|
|
140
|
+
@objc
|
|
141
|
+
func handleDeviceOrientationChange(notification: Notification) {
|
|
142
|
+
let newScreenOrientation = UIDevice.current.orientation.toInterfaceOrientation()
|
|
143
|
+
|
|
144
|
+
interfaceOrientationDidChange(newScreenOrientation)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
Called when the device is physically rotated. Checks if screen orientation should be changed after user rotated the device.
|
|
149
|
+
*/
|
|
150
|
+
func interfaceOrientationDidChange(_ newScreenOrientation: UIInterfaceOrientation) {
|
|
151
|
+
if currentScreenOrientation == newScreenOrientation || newScreenOrientation == .unknown {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if currentOrientationMask.contains(newScreenOrientation) {
|
|
156
|
+
// when changing orientation without changing dimensions traitCollectionDidChange isn't triggered so the event has to be called manually
|
|
157
|
+
if (newScreenOrientation.isPortrait && currentScreenOrientation.isPortrait)
|
|
158
|
+
|| (newScreenOrientation.isLandscape && currentScreenOrientation.isLandscape) {
|
|
159
|
+
screenOrientationDidChange(newScreenOrientation)
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// on iPads, traitCollectionDidChange isn't triggered at all, so we have to call screenOrientationDidChange manually
|
|
164
|
+
if isPad()
|
|
165
|
+
&& (newScreenOrientation.isPortrait && currentScreenOrientation.isLandscape
|
|
166
|
+
|| newScreenOrientation.isLandscape && currentScreenOrientation.isPortrait) {
|
|
167
|
+
screenOrientationDidChange(newScreenOrientation)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
Called by ScreenOrientationViewController when the dimensions of the view change.
|
|
174
|
+
Also used for Expo Go in EXAppViewController.
|
|
175
|
+
*/
|
|
176
|
+
@objc
|
|
177
|
+
public func traitCollectionDidChange(to traitCollection: UITraitCollection) {
|
|
178
|
+
currentTraitCollection = traitCollection
|
|
179
|
+
|
|
180
|
+
let currentDeviceOrientation = UIDevice.current.orientation.toInterfaceOrientation()
|
|
181
|
+
let currentOrientationMask = self.rootViewController?.supportedInterfaceOrientations ?? []
|
|
182
|
+
|
|
183
|
+
var newScreenOrientation = UIInterfaceOrientation.unknown
|
|
184
|
+
|
|
185
|
+
// We need to deduce what is the new screen orientaiton based on currentOrientationMask and new dimensions of the view
|
|
186
|
+
if traitCollection.isPortrait() {
|
|
187
|
+
// From trait collection, we know that screen is in portrait or upside down orientation.
|
|
188
|
+
let portraitMask = currentOrientationMask.intersection([.portrait, .portraitUpsideDown])
|
|
189
|
+
|
|
190
|
+
if portraitMask == .portrait {
|
|
191
|
+
// Mask allows only proper portrait - we know that the device is in either proper portrait or upside down
|
|
192
|
+
// we deduce it is proper portrait.
|
|
193
|
+
newScreenOrientation = .portrait
|
|
194
|
+
} else if portraitMask == .portraitUpsideDown {
|
|
195
|
+
// Mask allows only upside down portrait - we know that the device is in either proper portrait or upside down
|
|
196
|
+
// we deduce it is upside down portrait.
|
|
197
|
+
newScreenOrientation = .portraitUpsideDown
|
|
198
|
+
} else if currentDeviceOrientation == .portrait || currentDeviceOrientation == .portraitUpsideDown {
|
|
199
|
+
// Mask allows portrait or upside down portrait - we can try to deduce orientation
|
|
200
|
+
// from device orientation.
|
|
201
|
+
newScreenOrientation = currentDeviceOrientation
|
|
202
|
+
}
|
|
203
|
+
} else if traitCollection.isLandscape() {
|
|
204
|
+
// From trait collection, we know that screen is in landscape left or right orientation.
|
|
205
|
+
let landscapeMask = currentOrientationMask.intersection(.landscape)
|
|
206
|
+
|
|
207
|
+
if landscapeMask == .landscapeLeft {
|
|
208
|
+
// Mask allows only proper landscape - we know that the device is in either proper landscape left or right
|
|
209
|
+
// we deduce it is proper left.
|
|
210
|
+
newScreenOrientation = .landscapeLeft
|
|
211
|
+
} else if landscapeMask == .landscapeRight {
|
|
212
|
+
// Mask allows only landscape right - we know that the device is in either proper landscape left or right
|
|
213
|
+
// we deduce it is landscape right.
|
|
214
|
+
newScreenOrientation = .landscapeRight
|
|
215
|
+
} else if currentDeviceOrientation == .landscapeLeft || currentDeviceOrientation == .landscapeRight {
|
|
216
|
+
// Mask allows landscape left or right - we can try to deduce orientation
|
|
217
|
+
// from device orientation.
|
|
218
|
+
newScreenOrientation = currentDeviceOrientation
|
|
219
|
+
} else if currentDeviceOrientation == .portrait || currentDeviceOrientation == .portraitUpsideDown {
|
|
220
|
+
// If the desired orientation is .landscape but the device is in .portrait orientation it will rotate to .landscapeRight
|
|
221
|
+
newScreenOrientation = .landscapeRight
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
screenOrientationDidChange(newScreenOrientation)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
Called at the end of the screen orientation change. Notifies modules about the orientation change.
|
|
229
|
+
*/
|
|
230
|
+
func screenOrientationDidChange(_ newScreenOrientation: UIInterfaceOrientation) {
|
|
231
|
+
currentScreenOrientation = newScreenOrientation
|
|
232
|
+
for module in orientationListeners {
|
|
233
|
+
module?.screenOrientationDidChange(newScreenOrientation)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
func moduleWillDeallocate(_ module: ScreenOrientationModule) {
|
|
238
|
+
moduleInterfaceMasks.removeValue(forKey: module)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func registerModuleToReceiveNotification(_ module: ScreenOrientationModule) {
|
|
242
|
+
orientationListeners.append(module)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
func unregisterModuleFromReceivingNotification(_ module: ScreenOrientationModule) {
|
|
246
|
+
for i in (0..<orientationListeners.count).reversed() {
|
|
247
|
+
if orientationListeners[i] === module || orientationListeners[i] == nil {
|
|
248
|
+
orientationListeners.remove(at: i)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|