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