DeviceOrientation vs. ScreenSize vs StatusBar.isLandscape? --- NEW --- vs. SwuftUI GeometryReader?
SwiftUI Update - Xcode 12 and iOS 16
Another alternative for SwiftUI (if you only want to process the changes between landscape/portrait) is to intercept the geometryProxy in the ContentView and then save the value in a global variable that can be used throughout the app. The value will be updated every time there is a re-render of the contentView. This will allow you to avoid observing UIDevice Notifications.
var content: some View {
GeometryReader { geometry in
processDeviceOrientation(with: geometry)
myContentView()
}
}
}
func processDeviceOrientation(with geometry: GeometryProxy) {
let orientation: DeviceOrientation = geometry.size.width > geometry.size.height ? .landscape : .portrait
if orientation != deviceOrientation {
deviceOrientation = orientation
}
}
}
var deviceOrientation: DeviceOrientation = .unknown
enum DeviceOrientation {
case landscape, portrait, unknown
}
iOS 11, Swift 4 and Xcode 9.X
Regardless of using AutoLayout or not, there are several ways to get the right device orientation, and they could be used to detect rotation changes while using the app, as well as getting the right orientation at app launch or after resuming from background.
This solutions work fine in iOS 11 and Xcode 9.X
1. UIScreen.main.bounds.size:
If you only want to know if the app is in landscape or portrait mode, the best point to start is in viewDidLoad
in the rootViewController
at launch time and in viewWillTransition(toSize:)
in the rootViewController
if you want to detect rotation changes while the app is in background, and should resume the UI in the right orientation.
let size = UIScreen.main.bounds.size
if size.width < size.height {
print("Portrait: \(size.width) X \(size.height)")
} else {
print("Landscape: \(size.width) X \(size.height)")
}
This also happens early during the app/viewController life cycles.
2. NotificationCenter
If you need to get the actual device orientation (including faceDown, faceUp, etc). you want to add an observer as follows (even if you do it in the application:didFinishLaunchingWithOptions
method in the AppDelegate
, the first notifications will likely be triggered after the viewDidLoad
is executed
device = UIDevice.current
device?.beginGeneratingDeviceOrientationNotifications()
notificationCenter = NotificationCenter.default
notificationCenter?.addObserver(self, selector: #selector(deviceOrientationChanged),
name: Notification.Name("UIDeviceOrientationDidChangeNotification"),
object: nil)
And add the selector as follows. I split it in 2 parts to be able to run inspectDeviceOrientation()
in viewWillTransition
@objc func deviceOrientationChanged() {
print("Orientation changed")
inspectDeviceOrientation()
}
func inspectDeviceOrientation() {
let orientation = UIDevice.current.orientation
switch UIDevice.current.orientation {
case .portrait:
print("portrait")
case .landscapeLeft:
print("landscapeLeft")
case .landscapeRight:
print("landscapeRight")
case .portraitUpsideDown:
print("portraitUpsideDown")
case .faceUp:
print("faceUp")
case .faceDown:
print("faceDown")
default: // .unknown
print("unknown")
}
if orientation.isPortrait { print("isPortrait") }
if orientation.isLandscape { print("isLandscape") }
if orientation.isFlat { print("isFlat") }
}
Note that the UIDeviceOrientationDidChangeNotification
may be posted several times during launch, and in some cases it may be .unknown
. What I have seen is that the first correct orientation notification is received after the viewDidLoad
and viewWillAppear
methods, and right before viewDidAppear
, or even applicationDidBecomeActive
The orientation object will give you all 7 possible scenarios(from the enum UIDeviceOrientation
definition):
public enum UIDeviceOrientation : Int {
case unknown
case portrait // Device oriented vertically, home button on the bottom
case portraitUpsideDown // Device oriented vertically, home button on the top
case landscapeLeft // Device oriented horizontally, home button on the right
case landscapeRight // Device oriented horizontally, home button on the left
case faceUp // Device oriented flat, face up
case faceDown // Device oriented flat, face down
}
Interestingly, the isPortrait
read-only Bool
variable is defined in an extension to UIDeviceOrientation
as follows:
extension UIDeviceOrientation {
public var isLandscape: Bool { get }
public var isPortrait: Bool { get }
public var isFlat: Bool { get }
public var isValidInterfaceOrientation: Bool { get }
}
3. StatusBarOrientation
UIApplication.shared.statusBarOrientation.isLandscape
This also works fine to determine if orientation is portrait or landscape orientation and gives the same results as point 1. You can evaluate it in viewDidLoad
(for App launch) and in viewWillTransition(toSize:)
if coming from Background. But it won't give you the details of top/bottom, left/right, up/down you get with the notifications (Point 2)
UIDeviceOrientationDidChangeNotification
to get notified when device orientation get changed – PotUIApplication.sharedApplication().statusBarOrientation.isLandscape
– AmplificationUIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]
I have used this and this works for me. Let me know. – PotUIApplication.sharedApplication().statusBarOrientation
works but I am wondering whyUIDevice.currentDevice().orientation
not working when app launches. – GidestatusBarOrientation
andUIDevice.currentDevice().orientation
. From Apple docs onUIDeviceOrientation orientation: The value of the property is a constant that indicates the current orientation of the device. This value represents the physical orientation of the device and may be different from the current orientation of your application’s user interface.
You must be getting the correct value withUIDevice.currentDevice().orientation
– PotstatusBarOrientation
instead? – Gide