Difference of UIDeviceOrientation and UIInterfaceOrientation
It is important to know the difference of UIDeviceOrientation and UIInterfaceOrientation values, in order to determine what you really need in your application. Most of the time, you will need the UIInterfaceOrientation. But sometimes, especially when dealing with legacy or old style code, or sometimes poorly designed code which you cannot easily redesign or refactor, you will need to rely on UIDeviceOrientation to determine the current orientation.
An example of such poorly designed code is when you have a UIView which handles its own rotation. This is poorly designed because a UIView should not react directly to orientation changes. This is the job of the UIViewController.
Moving on, the UIDeviceOrientation is the current orientation of the device itself, not of the user interface currently displayed within the device. UIInterfaceOrientation, on the other hand, is the current orientation of the target interface. For example, if you use [[UIApplication sharedApplication] statusBarOrientation]
in Objective-C or UIApplication.sharedApplication().statusBarOrientation
in Swift, you will get the current orientation of the status bar.
There are cases where the UIDeviceOrientation says it is in landscape left, but UIInterfaceOrientation (e.g. status bar orientation) says it is in portrait. This type of case can happen when the currently displayed UIViewController does not rotate (for example, only portrait is supported) and then you try to rotate the device, which means that the actual device can be in landscape but the displayed UIViewController is still displayed in portrait.
In addition, UIDeviceOrientation has 7 possible values
Objective-C
1 2 3 4 5 6 7 8 9 10 |
// Objective-C typedef NS_ENUM(NSInteger, UIDeviceOrientation) { UIDeviceOrientationUnknown, UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left UIDeviceOrientationFaceUp, // Device oriented flat, face up UIDeviceOrientationFaceDown // Device oriented flat, face down }; |
Swift
1 2 3 4 5 6 7 8 9 10 |
// Swift 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 } |
while UIInterfaceOrientation has 5 possible values.
Objective-C
1 2 3 4 5 6 7 8 9 10 |
// Objective-C // Note that UIInterfaceOrientationLandscapeLeft is equal to UIDeviceOrientationLandscapeRight (and vice versa). // This is because rotating the device to the left requires rotating the content to the right. typedef NS_ENUM(NSInteger, UIInterfaceOrientation) { UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft }; |
Swift
1 2 3 4 5 6 7 8 9 10 |
// Swift // Note that UIInterfaceOrientationLandscapeLeft is equal to UIDeviceOrientationLandscapeRight (and vice versa). // This is because rotating the device to the left requires rotating the content to the right. enum UIInterfaceOrientation : Int { case Unknown case Portrait case PortraitUpsideDown case LandscapeLeft case LandscapeRight } |
You need to handle the other 2 values (FaceUp and FaceDown) explicitly when using UIDeviceOrientation when handling rotations. This will be discussed in the 3rd section, “How to handle UIDeviceOrientation values”.
How to get UIDeviceOrientation values and updates
You can get the current device orientation using
1 2 |
// Objective-C [[UIDevice currentDevice] orientation]; |
1 2 |
// Swift UIDevice.currentDevice().orientation |
Before using the above API to get orientation data, you must enable data delivery using the beginGeneratingDeviceOrientationNotifications method of the UIDevice. This method starts the accelerometer, so when you no longer need to track the device orientation, you must call the endGeneratingDeviceOrientationNotifications method to disable the delivery of notifications as well as the accelerometer. As a safety precaution, you can check if the app is generating UIDeviceOrientation change notifications using the isGeneratingDeviceOrientationNotifications method of the UIDevice. Also, if you want to be notified about the device orientation changes, you need to register for notifications for UIDeviceOrientationDidChangeNotification.
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// Objective-C #import "ViewController.h" @interface ViewController () @property (nonatomic) UIDeviceOrientation currentDeviceOrientation; @end @implementation ViewController - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceDidRotate:) name:UIDeviceOrientationDidChangeNotification object:nil]; // Initial device orientation self.currentDeviceOrientation = [[UIDevice currentDevice] orientation]; // Do what you want here } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver:self]; if ([[UIDevice currentDevice] isGeneratingDeviceOrientationNotifications]) { [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; } } - (void)deviceDidRotate:(NSNotification *)notification { self.currentDeviceOrientation = [[UIDevice currentDevice] orientation]; // Do what you want here } @end |
Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Swift class ViewController: UIViewController { var currentDeviceOrientation: UIDeviceOrientation = .Unknown override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications() NSNotificationCenter.defaultCenter().addObserver(self, selector: "deviceDidRotate:", name: UIDeviceOrientationDidChangeNotification, object: nil) // Initial device orientation self.currentDeviceOrientation = UIDevice.currentDevice().orientation // Do what you want here } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) NSNotificationCenter.defaultCenter().removeObserver(self) if UIDevice.currentDevice().generatesDeviceOrientationNotifications { UIDevice.currentDevice().endGeneratingDeviceOrientationNotifications() } } func deviceDidRotate(notification: NSNotification) { self.currentDeviceOrientation = UIDevice.currentDevice().orientation // Do what you want here } } |
Important: You must always match your beginGeneratingDeviceOrientationNotifications call with an endGeneratingDeviceOrientationNotifications call!
In a personal iOS app I was working on before, I was calling beginGeneratingDeviceOrientationNotifications on iPad, but not on iPhone. Then on dealloc of that class, I was calling endGeneratingDeviceOrientationNotifications regardless if device is an iPad or an iPhone. I thought back then that this will not affect anything, and if not more, will ensure that the accelerometer gets shut down, if it gets mistakenly activated even on an iPhone. The result of this is that after the class has been deallocated, which means that endGeneratingDeviceOrientationNotifications has been called even if beginGeneratingDeviceOrientationNotifications was not, the rotation of all the screens in the app did not work anymore. A very tricky and not an obvious problem, so watch out for this.
How to handle UIDeviceOrientation values
If you want to use UIDeviceOrientation to rotate some of your views, then you need to ignore some of the UIDeviceOrientation values. Specifically, you need to ignore the Unknown, FaceUp, and FaceDown values. Luckily, the iOS SDK has the macro UIDeviceOrientationIsValidInterfaceOrientation which returns YES only if the specified UIDeviceOrientation is Portrait, PortraitUpsideDown, LandscapeLeft, and LandscapeRight.
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Objective-C - (void)deviceDidRotate:(NSNotification *)notification { UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation]; // Ignore changes in device orientation if unknown, face up, or face down. if (!UIDeviceOrientationIsValidInterfaceOrientation(currentOrientation)) { return; } BOOL isLandscape = UIDeviceOrientationIsLandscape(newOrientation); BOOL isPortrait = UIDeviceOrientationIsPortrait(newOrientation); // Rotate your view, or other things here } |
Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Swift func deviceDidRotate(notification: NSNotification) { let currentOrientation = UIDevice.currentDevice().orientation // Ignore changes in device orientation if unknown, face up, or face down. if !UIDeviceOrientationIsValidInterfaceOrientation(currentOrientation) { return; } let isLandscape = UIDeviceOrientationIsLandscape(currentOrientation); let isPortrait = UIDeviceOrientationIsPortrait(currentOrientation); // Rotate your view, or other things here } |
Note that in effect, the last retrieved UIDeviceOrientation will be processed by your code. The possile flow of execution when using this code will look like this:
- User rotates the device to Landscape Left.
The UIDeviceOrientation that you will get is LandscapeLeft. Since this is a valid UIDeviceOrientation for us, then we process this and rotate our view. - User rotates the device to FaceUp (meaning device is on its back).
Since you ignore FaceUp, nothing will be processed, so your views will stay in landscape. - User rotates the device to Portrait.
The UIDeviceOrientation that you will get is Portrait. Since this is a valid UIDeviceOrientation for us, then we process this and rotate our view. - And so on.
Conclusion
Using UIDeviceOrientation may not be the best way to get the orientation of the views, but sometimes this is still needed. Following this guide should help you get started, and most of the time, this is all you will need. Just be sure to read through the important points. Drop by in the comments section if this post helped.
Your code for skipping faceup and facedown orientations was quite helpful. Thanks for the idea!