iPhone rotation woes and a workaround

My story's often told: app meets UITabBarController, UITabBarController starts an affair with multiple UINavigationControllers, the UINavigationControllers end up with a host of child UIViewControllers, some of which need to rotate to landscape mode and some that don't.

So far, so good.

Then, the plot thickens! In comes an MFMailComposeViewComposer that you want to display via presentModalViewController. Your view's in landscape, you tap the button that brings up the modal mail compose screen and – oh, the injustice, the tragedy – it flips your view to portrait mode before displaying itself.

Bummer!

Of course, there's more to the story. You had started rotating your views yourself using CGAffineTransforms when you saw that getting autorotation to work via shouldRotateToInterfaceOrientation for complicated view hierarchies required a four year degree in Voodoo. Now, you feel you may be paying for your insolence for deviating from the One True Path of the Church of the Holy Apple DDFS. (I don't know what DDFS stands for but it makes names cooler when they have random letters after them.)

So you dig deeper. It appears that your view controllers all return UIInterfaceOrientationPortrait when queried for their interfaceOrientation properties, even when the device is in one of the landscape modes. (Note the subtle impedance mismatch between "interface" and "device" in the previous sentence, it's a clue!) Aha, and it appears that either presentModalViewController or MFMailComposeViewController is looking for that value and, being the control freak that it is, actually forcing the layout to that orientation before displaying itself.

Well, there's another control freak in town, buddy, and there ain't room enough for the both of us!

So what's a control freak with a scripting background to do but monkey patch this baby… categories to the rescue! If I can get the interfaceOrientation method to return the actual orientation of the device, instead of the interfaces's orientation, it should work!

@interface UIViewController(OrientationPatch)
-(UIDeviceOrientation)interfaceOrientation;
@end

@implementation UIViewController(OrientationPatch)

-(UIDeviceOrientation)interfaceOrientation
{
  return [[UIDevice currentDevice] orientation];
}

And it does… somewhat. Now the MFMailComposeViewController is displaying properly but reverts the app to portrait when it is dismissed! Doh!

More investigating and it appears that shouldRotateToInterfaceOrientation is now being called on my UITabBarController and, since it wasn't implemented, is now returning NO to everything.

I know how to fix that: subclass UITabBarController and implement shouldRotateToInterfaceOrientation:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
  return YES;
}

Et, voilà, the MFMailComposeViewController now displays perfectly in landscape mode and excuses itself gracefully without blowing down the whole house.

None of this should be considered great advice to follow. Having found shouldRotateToInterfaceOrientation too painful to work with in my view hierarchy, I resorted to rotating my views myself (since I support rotation in just one, clearly defined section of my application, this has so far proven much easier to implement). If you can get things working with shouldRotateToInterfaceOrientation, you should stick to that. If you, however, find yourself in rotation hell like I did, maybe some of the above will make sense to you and lead you to diagnose or even fix the issues you may be encountering.

Regardless, it definitely feels like autorotation support, especially selective autorotation support for complex view hierarchies, is something that the iPhone SDK team could concentrate a bit on for future releases.

Comments