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.
Files changed (32) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +8 -1
  3. package/android/build.gradle +8 -10
  4. package/android/src/main/AndroidManifest.xml +1 -1
  5. package/android/src/main/java/expo/modules/screenorientation/ScreenOrientationModule.kt +7 -3
  6. package/build/ExpoScreenOrientation.d.ts.map +1 -1
  7. package/build/ExpoScreenOrientation.js +2 -5
  8. package/build/ExpoScreenOrientation.js.map +1 -1
  9. package/expo-module.config.json +2 -1
  10. package/ios/{EXScreenOrientation.podspec → ExpoScreenOrientation.podspec} +9 -2
  11. package/ios/{EXScreenOrientation/ScreenOrientationAppDelegate.swift → ScreenOrientationAppDelegate.swift} +5 -3
  12. package/ios/ScreenOrientationExceptions.swift +16 -0
  13. package/ios/ScreenOrientationModule.swift +118 -0
  14. package/ios/ScreenOrientationRNSScreenWindowTraits.h +11 -0
  15. package/ios/{EXScreenOrientation/ScreenOrientationReactDelegateHandler.swift → ScreenOrientationReactDelegateHandler.swift} +1 -1
  16. package/ios/ScreenOrientationRegistry.swift +252 -0
  17. package/ios/ScreenOrientationUtilities.swift +110 -0
  18. package/ios/ScreenOrientationViewController.swift +64 -0
  19. package/ios/enums/ModuleOrientation.swift +39 -0
  20. package/ios/enums/ModuleOrientationLock.swift +60 -0
  21. package/package.json +2 -2
  22. package/src/ExpoScreenOrientation.ts +2 -5
  23. package/ios/EXScreenOrientation/EXScreenOrientationModule.h +0 -11
  24. package/ios/EXScreenOrientation/EXScreenOrientationModule.m +0 -177
  25. package/ios/EXScreenOrientation/EXScreenOrientationRegistry.h +0 -46
  26. package/ios/EXScreenOrientation/EXScreenOrientationRegistry.m +0 -272
  27. package/ios/EXScreenOrientation/EXScreenOrientationUtilities.h +0 -25
  28. package/ios/EXScreenOrientation/EXScreenOrientationUtilities.m +0 -172
  29. package/ios/EXScreenOrientation/EXScreenOrientationViewController.h +0 -14
  30. package/ios/EXScreenOrientation/EXScreenOrientationViewController.m +0 -88
  31. package/ios/EXScreenOrientation/NSString+UIInterfaceOrientationMask.h +0 -9
  32. 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
- # expo-screen-orientation
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
 
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '5.2.0'
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 '5.2.0'
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 package="expo.modules.screenorientation">
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() = appContext.activityProvider?.currentActivity ?: throw Exceptions.MissingActivity()
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
- currentActivity.requestedOrientation = it
78
+ weakCurrentActivity?.requestedOrientation = it
75
79
  }
76
80
  }
77
81
  }
78
82
 
79
83
  override fun onHostResume() {
80
- initialOrientation = initialOrientation ?: currentActivity.requestedOrientation
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":";AAGA,wBAEmD"}
1
+ {"version":3,"file":"ExpoScreenOrientation.d.ts","sourceRoot":"","sources":["../src/ExpoScreenOrientation.ts"],"names":[],"mappings":";AAEA,wBAA4D"}
@@ -1,6 +1,3 @@
1
- import { NativeModulesProxy, requireNativeModule } from 'expo-modules-core';
2
- import { Platform } from 'react-native';
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,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,eAAe,QAAQ,CAAC,EAAE,KAAK,SAAS;IACtC,CAAC,CAAC,mBAAmB,CAAC,uBAAuB,CAAC;IAC9C,CAAC,CAAC,kBAAkB,CAAC,qBAAqB,IAAI,EAAE,CAAC","sourcesContent":["import { NativeModulesProxy, requireNativeModule } from 'expo-modules-core';\nimport { Platform } from 'react-native';\n\nexport default Platform.OS === 'android'\n ? requireNativeModule('ExpoScreenOrientation')\n : NativeModulesProxy.ExpoScreenOrientation || {};\n"]}
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"]}
@@ -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 = 'EXScreenOrientation'
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 = "#{s.name}/**/*.{h,m,swift}"
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
- if let screenOrientationRegistry = ModuleRegistryProvider.getSingletonModule(for: EXScreenOrientationRegistry.self) as? EXScreenOrientationRegistry {
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 EXScreenOrientationViewController(defaultScreenOrientationFromPlist: ())
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
+ }