简体   繁体   中英

How to determine if iOS device's orientation *actually* changes?

I am writing this code to make some changes to how my app is laid out depending on if it is in landscape or portrait mode. I have set up the app to get a notification on the orientation changing like this:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedRotate:) name:UIDeviceOrientationDidChangeNotification object:NULL];

And then defining receivedRotate:

-(void) receivedRotate: (NSNotification*) notification {
    UIDeviceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation];

    // ... do something
}

However, UIDeviceOrientationDidChangeNotification is called whenever the device is moved slightly. Eg, the UIDeviceOrientation can be any of the following:

UIDeviceOrientationPortrait,
UIDeviceOrientationPortraitUpsideDown,
UIDeviceOrientationLandscapeLeft,
UIDeviceOrientationLandscapeRight,
UIDeviceOrientationFaceUp,
UIDeviceOrientationFaceDown

This is great but I just want to know if the device orientation is in portrait or landscape, and I also only want to run this code when the orientation actually changes - from portrait to landscape or vice-versa. If they turn the phone all the way around, it is still in landscape mode - just a different form of landscape mode. What's the ideal way to make sure that my rotation code only runs when it is necessary?

DeviceOrientation vs. ScreenSize vs StatusBar.isLandscape?

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 = Notifi`enter code here`cationCenter.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(toSize:)

@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)

I believe what you're looking for is UIInterfaceOrientation , not UIDeviceOrientation . To use this, just implement the following function in your view controller.

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
    //
}

This enum is declared as the following

typedef enum : NSInteger {
   UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
   UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
   UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
   UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} UIInterfaceOrientation;

将先前的方向保存在属性中,并将其与当前方向进行比较。

@0x7ffffff's and @Kunal Balani's answers are down the right path, but I'd recommend you take a look at UIViewController's documentation for rotation handling on iOS 6+ applications vs how it's handled in iOS 5 and older, since you don't mention whether or not you're supporting iOS 5 (which at the time of writing of this answer is still supported). This will help resolve any issues you could run into down the road. I'd recommend specifically taking a look at willRotateToInterfaceOrientation:duration: / willAnimateRotationToInterfaceOrientation:duration: / didRotateFromInterfaceOrientation: and when they are called during the lifecycle of a rotation.

You will definitely want to store the previous orientation as mentioned in the other answers so you don't run your code more than once.

As for your specific question on more "agnostic" as to what version of landscape vs portrait, UIKit's function reference contains a number of pertinent macros, specifically UIInterfaceOrientationIsPortrait() and UIInterfaceOrientationIsLandscape() .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM