In Swift, how to get the device orientation correctly right after it's launched?
Asked Answered
G

5

12

I use following code to see if the device is in landscape mode or not:

UIDevice.currentDevice().orientation.isLandscape.boolValue

It works BUT if I put my device in landscape mode before the app is launched, and after viewDidLoad, I call this line of code, it always returns false.

If I use this instead:

interfaceOrientation.isLandscape

it returns true, which is correct, but the compiler is showing a warning that interfaceOrientation was deprecated in iOS 8.0.

What is the correct way to get the device orientation right after the app is launched?

Gide answered 24/12, 2015 at 12:13 Comment(9)
your app supports only one orientation?Amplification
The app supports both portrait and landscape. I put the device in landscape mode, the first line returns false. If I rotate, it starts to return the right value.Gide
interfaceOrientation and deviceOrientation are two different things. Use UIDeviceOrientationDidChangeNotification to get notified when device orientation get changedPot
what about UIApplication.sharedApplication().statusBarOrientation.isLandscapeAmplification
@Pot I have no problem to get orientation notifications. I like to know the orientation when the app launches.Gide
Use this: UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation] I have used this and this works for me. Let me know.Pot
@Amplification UIApplication.sharedApplication().statusBarOrientation works but I am wondering why UIDevice.currentDevice().orientation not working when app launches.Gide
Please don't get confuse with statusBarOrientation and UIDevice.currentDevice().orientation. From Apple docs on UIDeviceOrientation 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 with UIDevice.currentDevice().orientationPot
@Pot in your last statement, do you mean statusBarOrientation instead?Gide
H
2

I have tested many times about orientation, so I have summed up some experience.

In all iPhone devices, except iPhone6(s) plus, the only interface orientation is .portrait. If App is launched in landscape mode, there must be a change of orientation. One will receive the UIDeviceOrientationDidChangeNotification. It's an appropriate time to get the orientation.

Regarding the launching when in landscape with iPhone6, the orientation after the launch will change once: enter image description here

The launching when in landscape with iPhone6 plus, after launch the orientation never changed: enter image description here

Two different screenshot with the same app, enter image description here

So before the app does change orientation, the orientation is still like in the home page.

In viewDidLoad, the orientation has not changed yet, the log will be the wrong direction.

Heady answered 24/12, 2015 at 13:36 Comment(0)
G
13

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)

Globetrotter answered 1/3, 2018 at 20:59 Comment(1)
#3 worked well for me - I wanted two distinct states, landscape vs portrait, in order to do some very specific UI updates. Using the device orientation would mess it up when the device became flat.Assize
B
5

This worked for me:

if UIScreen.main.bounds.width > UIScreen.main.bounds.height{
    print("Portraitmode!")
}

It works on all devices based on the display dimensions: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html

Belong answered 27/8, 2019 at 15:11 Comment(2)
works for me this is the best way to avoid the orientation "face up or flat"Wolfgang
not working me..Tole
B
3

The isValidInterfaceOrientation should be detected before checking the orientation isLandscape. Don't process the flat message with isValidInterfaceOrientation == false (when it has any value of isLandscape).

I had a hazzel with this until I read the topic more carefully. With consideration of the isValidInterfaceOrientation it works fine.

@objc func rotated() {
    if (UIDevice.current.orientation.isValidInterfaceOrientation) {
        if (UIDevice.current.orientation.isLandscape) { 
            if(!bLandscape) {
                bLandscape = true
                setupTabBar() // Repaint the app
            }
        } else { // Portait 
            if(bLandscape) {
                bLandscape = false
                setupTabBar() // Repaint the app 
            }
        }
    }
}
Billmyre answered 7/6, 2020 at 18:6 Comment(1)
This solution does not work for me when the phone orientation is locked..I want to detect the orientation change when the Phone Orientation is locked. Is there a way that is possible?Tenuous
H
2

I have tested many times about orientation, so I have summed up some experience.

In all iPhone devices, except iPhone6(s) plus, the only interface orientation is .portrait. If App is launched in landscape mode, there must be a change of orientation. One will receive the UIDeviceOrientationDidChangeNotification. It's an appropriate time to get the orientation.

Regarding the launching when in landscape with iPhone6, the orientation after the launch will change once: enter image description here

The launching when in landscape with iPhone6 plus, after launch the orientation never changed: enter image description here

Two different screenshot with the same app, enter image description here

So before the app does change orientation, the orientation is still like in the home page.

In viewDidLoad, the orientation has not changed yet, the log will be the wrong direction.

Heady answered 24/12, 2015 at 13:36 Comment(0)
D
0

I had a problem to detect which orientation was before isFlat so I put this in my view controller

let orientation = UIDevice.current.orientation

override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    if  orientation.isPortrait {
        return .portrait
    } else if orientation.isFlat{
        if UIScreen.main.bounds.width < UIScreen.main.bounds.height{
            return .portrait
        } else {
            return .landscape
        }
    } else {
        return .landscape
    }
}
Disillusion answered 17/7, 2019 at 6:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.