49

Simple put, I was relying on the following code to provide the orientation of the application. This is utilized for several reasons within the application:

  • Per UX specification, the layout of the stackview is set based upon orientation for iPad (horizontal when in landscape, vertical when in portrait)
  • Building on the previous item, the stackview is placed on the screen to the left (portrait) or on the top (landscape)
  • There is custom rotation logic that responds differently based on the status. tl;dr is that on an iPad, the app presents itself with significant differences between orientation

I am just tasked with maintenance in this scenario and do not have the ability to make significant changes that deviate from the current (and properly functioning) layout logic in place.

As of now, it relies upon the following to capture application orientation:

var isLandscape: Bool {
    return UIApplication.shared.statusBarOrientation.isLandscape
}

However, with the Xcode 11 GM version, I am given the following deprecation warning:

'statusBarOrientation' was deprecated in iOS 13.0: Use the interfaceOrientation property of the window scene instead.

How can I go about getting the orientation of the application via status bar?

CodeBender
  • 35,668
  • 12
  • 125
  • 132
  • 1
    It would help to know why you need to know the app's orientation. In most cases it shouldn't matter. – rmaddy Sep 17 '19 at 00:42
  • Please provide some reasoning for why. Perhaps someone way find another way to solve your problem without needing to know orientation. You might use AutoLayout, screen size, etc. By telling us more, you enable us to more fully answer the question. Thanks – AlexH Sep 17 '19 at 00:44
  • @rmaddy Sorry for the delay, I have updated the question – CodeBender Sep 18 '19 at 16:13
  • @AlexH Sorry for the delay, I have updated the question – CodeBender Sep 18 '19 at 16:13
  • None of that should be based on device orientation. It should all be based on the size of the view controller. Consider supporting multi-tasking on an iPad. The iPad might be landscape but your app may be tall and skinny. This is why all of the orientation stuff has been deprecated. It's no longer relevant. – rmaddy Sep 18 '19 at 16:21
  • @rmaddy The app has `UIRequiresFullScreen` set to `YES`, which should prevent that. A key reason is due to performance (frankly, the app is a resource hog & that will not change due to what it does). While I am definitely open to removing this check, I am curious how that would play in if it is not possible to split? Would just checking dimensions for the screen be sufficient in that case? – CodeBender Sep 18 '19 at 16:32
  • 1. I believe in March you will no longer be allowed to set that to Yes. 2. That doesn't change the fact that your decision should be based on the view size, not the device orientation. If your view controller's size is wider than it is tall, assume a landscape layout. Simple and it works in all cases. – rmaddy Sep 18 '19 at 16:36
  • @rmaddy Perfect, thanks! Will have to table this for later but once I get that updated, will update the answer below to reflect this. Thanks again – CodeBender Sep 18 '19 at 16:37

11 Answers11

35

Swift 5, iOS 13, but compatible with older versions of iOS:

extension UIWindow {
    static var isLandscape: Bool {
        if #available(iOS 13.0, *) {
            return UIApplication.shared.windows
                .first?
                .windowScene?
                .interfaceOrientation
                .isLandscape ?? false
        } else {
            return UIApplication.shared.statusBarOrientation.isLandscape
        }
    }
}

Usage:

if (UIWindow.isLandscape) {
    print("Landscape")
} else {
    print("Portrait")
}
Sasho
  • 3,532
  • 1
  • 31
  • 30
29

Swift 5, iPadOS 13, taking the multi-window environment into account:

if let interfaceOrientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation {
 // Use interfaceOrientation
}
Adam
  • 1,249
  • 1
  • 10
  • 11
19

This is how I do it for iOS13 for Swift 5.1:

var statusBarOrientation: UIInterfaceOrientation? {
    get {
        guard let orientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation else {
            #if DEBUG
            fatalError("Could not obtain UIInterfaceOrientation from a valid windowScene")
            #else
            return nil
            #endif
        }
        return orientation
    }
}
Geoff H
  • 3,107
  • 1
  • 28
  • 53
  • 7
    For testing with debug releases, you do not need to test for debug and use fatalError, as asserts are intended for the behavior you desire here. An assert will only work in debug and gets the same result (a crash with an optional error message). https://developer.apple.com/documentation/swift/1541112-assert – CodeBender Sep 27 '19 at 18:08
  • Your code is the only example that actually worked, great find! Something to note, is that if you call this command from within override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {} it will give you the old orientation right before it's about to change, so you just have to account for this. – Joseph Astrahan Jan 10 '20 at 22:27
11

Objective C, iOS 13 and compatible with older versions:

+ (BOOL)isLandscape
{
    if (@available(iOS 13.0, *)) {
        UIWindow *firstWindow = [[[UIApplication sharedApplication] windows] firstObject];
        if (firstWindow == nil) { return NO; }

        UIWindowScene *windowScene = firstWindow.windowScene;
        if (windowScene == nil){ return NO; }

        return UIInterfaceOrientationIsLandscape(windowScene.interfaceOrientation);
    } else {
        return (UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation));
    }
}

I added this to a UIWindow Category.

banana1
  • 545
  • 1
  • 4
  • 16
8

I came up with the following solution, but am open to improvements or suggestions should I be making some mistake I am not aware of:

var isLandscape: Bool {
    return UIApplication.shared.windows
        .first?
        .windowScene?
        .interfaceOrientation
        .isLandscape ?? false
}
CodeBender
  • 35,668
  • 12
  • 125
  • 132
  • 1
    UIApplication.shared.windows.first is the main window, so what if orientation changed in the second window (last). Is it gonna be the same? – Malith Kuruwita May 09 '21 at 13:32
3

None of the above answers didn't work for me, but this does. Swift 5 ioS 13.2

Your application should allow working in both portrait and landscape to use the below code, otherwise, results will be different

windows.first is main window windows.last is your current window

struct Orientation {
    // indicate current device is in the LandScape orientation
    static var isLandscape: Bool {
        get {
            return UIDevice.current.orientation.isValidInterfaceOrientation
                ? UIDevice.current.orientation.isLandscape
                : (UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isLandscape)!
        }
    }
    // indicate current device is in the Portrait orientation
    static var isPortrait: Bool {
        get {
            return UIDevice.current.orientation.isValidInterfaceOrientation
                ? UIDevice.current.orientation.isPortrait
                : (UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isPortrait)!
        }
    }
}
Malith Kuruwita
  • 594
  • 1
  • 9
  • 14
  • The device orientation may not be the same as the application window orientation, so you should not rely on that. – fishinear May 05 '21 at 18:27
  • @fishinear Application window orientation can be anything, regardless of device orientation. Device Orientation is the actual physical orientation of the device you are holding, it is what it is. If you are holding in Portrait, it is Portrait. But just because the device is in Portrait doesn't mean your interface is also in portrait. You may require your app to only provide landscape orientation, hence the interface orientation would be different than the device orientation. so keep that in mind when you use the above example. – Malith Kuruwita May 09 '21 at 13:00
2
UIApplication.shared.windows.first!.windowScene!.interfaceOrientation

Given that your application does not support multiple scenes.

Gizmodo
  • 3,151
  • 7
  • 45
  • 92
1

Swift 5, iOS 13

guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, windowScene.activationState == .foregroundActive, let window = windowScene.windows.first else { return }

switch windowScene.interfaceOrientation {
case .unknown:
    print("unknown")

case .portrait:
    print("portrait")

case .portraitUpsideDown:
    print("portraitUpsideDown")

case .landscapeLeft:
    print("landscapeLeft")

case .landscapeRight:
    print("landscapeRight")

@unknown default:
    print("default")
}
Michael
  • 293
  • 3
  • 12
1
import UIKit

public extension UIInterfaceOrientation {
    static var current: UIInterfaceOrientation? {
        guard let orientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation else {
            let errorMessage = "Could not obtain UIInterfaceOrientation from a valid windowScene"
            #if DEBUG
            assertionFailure(errorMessage)
            #else
            // TODO: Report Error with your error reporting tool.
            #endif
            return nil
        }
        return orientation
    }
}
Darkwonder
  • 1,149
  • 1
  • 13
  • 26
1

Try like this. It works fine. Xcode 13.2.1 - iOS 15.2 - Swift 5.5.2

let deviceOrientation = UIDevice.current.orientation {UIDeviceOrientation}

-1

Isn't this easier? (Tested with Swift 5, iPadOS 13.4)

var isLandscape: Bool {
    return UIDevice.current.orientation.isLandscape
} 

or basically:

let o = UIDevice.current.orientation
if o == .landscapeLeft || o == .landscapeRight {
    // Use interfaceOrientation
}

Second variation:

if UIDevice.current.orientation.isLandscape {
    // Use interfaceOrientation
}
Flori
  • 2,453
  • 1
  • 21
  • 31