Detecting orientation

I was surprised to learn today that [[UIDevice currentDevice] orientation] doesn’t always give the correct orientation. In fact, it seems to consistently fail whenever it’s called early in the app launching process. What’s up with that?

What’s going wrong

If you command-double click on UIDeviceOrientation, you’ll see that this type is an enum defined in UIDevice.h as follows:

(It’s nice that they include FaceUp and FaceDown orientations, as these avoid a situation where the actual position is arbitrarily close to four different orientations – a mathematically unstable state where very slight movement could cause constant UI rotations.)

What can go wrong in checking UIDevice’s orientation property is that it can return UIDeviceOrientationUnknown – and that’s exactly what it does if you observe this value early in your app.

If you read the UIDevice class reference, it says:

You also use the UIDevice instance to detect changes in the device’s characteristics, such as physical orientation. You get the current orientation using the orientation property or receive change notifications by registering for the UIDeviceOrientationDidChangeNotification notification. Before using either of these techniques to get orientation data, you must enable data delivery using the beginGeneratingDeviceOrientationNotifications method. When you no longer need to track the device orientation, call the endGeneratingDeviceOrientationNotifications method to disable the delivery of notifications.

How not to fix it

So it kinda sounds like you just need to call beginGeneratingDeviceOrientationNotifications early in the code to set things up.

Nope.

Based on my tests, this doesn’t work, even if it’s the first call in your code — early calls to the orientation property still return unknown.

My guess is that the accelerometer has a per-app data history, and this buffer is empty at app start-up.  So UIDevice just needs some time to get enough data to return an accurate orientation.  (The accelerometer’s data is pretty noisy, so it’s not reliable to make an orientation guess based on a couple data points.)

What to do?

Using UIScreen?

You can use [[UIScreen mainScreen] bounds] to get the current screen resolution.  You might think you could grab at least a portrait-vs-landscape boolean from this, but you can’t because these bounds don’t change per orientation.

Using UIAccelerometer?

In theory, you could use the accelerometer directly to gather some data and figure out what’s up, quite literally (hardee har har).  But this approach is far messier than the solution below for a number of reasons: it needs a lot more code, it might give you a result that’s inconsistent with the launch image the OS displays automatically for you (iPad launch images are orientation-specific), and it’s resolving an already-solved problem.

How yes to fix it

UIViewController is nice enough to provide an interfaceOrientation property.  To use it, you just have to refer to self.interfaceOrientation in your view controller – very easy!  And, unlike UIDevice’s version, this value is never unknown.  It is also automatically updated on rotations that you agree to per your shouldAutorotateToInterfaceOrientation: method.

My guess is that this property is set by the platform on app launch to be consistent with the launch image, so it would be a good practice to consistently use this single property for all orientation needs in your view controllers.  This way you don’t have to worry about unknown values, and your UI will behave in a consistent and OS-friendly manner.

As a quick final note, it’s good to know about the UIDeviceOrientationIs{Portrait,Landscape} macros.  These are very handy in concisely checking if your orientation is portrait or landscape.  You can use them like this:

Hope that helps!

8 Comments

  1. Felix Stehli
    Posted June 4, 2010 at 12:22 am | Permalink

    Thanks a lot, you saved my day!

  2. Christer
    Posted January 28, 2011 at 4:50 am | Permalink

    You just saved my day too ;-) Thanks!!

  3. Posted May 3, 2011 at 9:34 am | Permalink

    I found a trick to solve the FaceUp orientation issue!!!

    Delay the orientation check till AFTER the app has started running, then set variables, view sizes, etc.!!!

    //CODE

    – (void)viewDidLoad {

    [super viewDidLoad];

    //DELAY
    [NSTimer scheduledTimerWithTimeInterval:0.5
    target:self
    selector:@selector(delayedCheck)
    userInfo:nil
    repeats:NO];

    }

    -(void)delayedCheck{

    //DETERMINE ORIENTATION
    if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait ){
    FACING = @”PU”;
    }
    if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown ){
    FACING = @”PD”;
    }
    if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft ){
    FACING = @”LL”;
    }
    if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight ){
    FACING = @”LR”;
    }
    //DETERMINE ORIENTATION

    //START
    [self setStuff];
    //START

    }

    -(void)setStuff{

    if( FACING == @”PU” ){
    //logic for Portrait
    }
    else
    if( FACING == @”PD” ){
    //logic for PortraitUpsideDown
    }
    else{
    if( FACING == @”LL”){
    //logic for LandscapeLeft
    }
    else
    if( FACING == @”LR” ){
    //logic for LandscapeRight
    }

    }

    //CODE

    You can addSubviews, position elements, etc. in the ‘setStuff’ function … anything that would initially depend on the orientation!!!

    :D

    -Chris Allinson

  4. Posted May 6, 2011 at 1:13 pm | Permalink

    Hi @Chris – I’m not sure what the FaceUp issue is. What is the problem you’re solving?

  5. pim
    Posted June 11, 2011 at 12:48 am | Permalink

    This doesn’t seem to fix anything over here. I get 1 (portrait) at launch in each orientation for my view controller’s interfaceOrientation property. I also doubt that it’s different from [[UIApplication sharedApplication] statusBarOrientation], which is also, consistently 1 on my iPad (2) running iOS 4.3.

  6. Posted June 16, 2011 at 2:15 pm | Permalink

    @pim, you might want to check your app’s Info.plist file to check that you are supporting all orientations. I definitely see different values at launch in my view controller’s interfaceOrientation property.

  7. Posted July 4, 2011 at 4:38 am | Permalink

    Chris Allinson, thanks, that works.

  8. Posted July 7, 2011 at 3:22 am | Permalink

    @Janos, I still don’t understand the point of Chris’s code. What is the problem it solves? It seems like a bad approach to me because it requires waiting for some time before you “know” the orientation, which is essentially the problem my blog post is addressing.

One Trackback

  1. [...] and to use the helpful macros designed to work with the type UIInterfaceOrientation (see here for more details on that).  To keep your code consistent, it makes sense to use [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*