I haven’t worried about device orientation in a while. Supporting a variety of orientations is *nice*.
On iPad, supporting all orientations is strongly recommended; not supporting ‘upside down’ variants of your orientation is rarely tolerated(*). Unless you have a good reason not to (e.g. accelerometer freak app).
So I thought I’d rehearse a little. I learned new things and discovered an iOS 4.0 cherry that helps avoiding orientation caveats: UIWindow’s rootViewController property.
(*) Keep safe from harm.
How to support device orientation changes?
- Edit the info.plist file for your target
- Your views should be managed by view controllers. You can do without view controllers but then you’re pretty much on your own although at least there’s a system callback to tell you the orientation changed (seriously, you can find it yourself).
- Your view controllers should override shouldAutorotateToInterfaceOrientation.
- Your nib files should be nicely configured so your widgets won’t fly around in a happy mess when rotated.
- Know more about how views are managed, and how view controllers relate to the key window (see caveats)
info.plist
Specify supported orientations in the info.plist for your target:
- Select your project in the navigator
- Select your target
- Press/Depress orientation toggles in the summary tab (big icons showing rotated devices).
view controller
Override this and return YES to your supported orientations:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
interfaceOrientation may take one of the following values:
UIDeviceOrientationLandscapeLeft, UIDeviceOrientationLandscapeRight, UIDeviceOrientationPortrait,
UIDeviceOrientationPortraitUpsideDown
Sadly this appears to have a life of its own. Setting the info.plist property doesn’t further restrict a view controller’s response to orientation changes. In other words your plist may say that you only support landscape left/right. If a VC returns yes to all orientations, this won’t prevent its views from rotating.
Configure your nib files
A savvy approach to managing rotation is to create documents that look good both in portrait and landscape mode. It is possible to use different nib files for different orientations (not covering this here).
- In the attributes inspector for your document’s main view, set ‘Autoresize subviews’ to true.
- In the size inspector, use the autosizing toggles to determine what should happen to each element when the view rotates. You (or your UI designer) should do this for each element in the UI, deciding whether that element should size to fit / use a fixed size, and how to anchor the element (left, right, top-left, bottom, …).
- You can toggle the current ‘simulated’ orientation of a view using the orientation setting in the attributes inspector.
You can remove/resize elements programmatically (check Apple docs as referred below).
Caveats
In a worse case scenario, changing device orientation will do little other than moving the status bar (if there is one). Two possibilities:
- Your view controller isn’t notified of the change, so it can’t rotate its view.
- Your view isn’t managed by a view controller.
Read about custom view controllers (link to SDK docs). Under “Presenting a view controller’s view”:
” [...] use only the suggested techniques for displaying the views of your view controllers. In order to present and manage views properly, the system makes a note of each view (and its associated view controller) that you display directly or indirectly. [...] When the device orientation changes, a window uses this information to [notify] the frontmost view controller. If you incorporate a view [...] by other means (by adding it as a subview to some other view perhaps), the system assumes you want to manage the view yourself [...]“
And this, under “Understanding the rotation process”:
“[...] the window object does much of the work associated with changing the current orientation. [...] Specifically, it works with the view controller whose root view was most recently added to, or presented in, the window.”
Only one view controller will receive the event, and propagate it to it’s (unique) view (and the underlying hierarchy).
Note: if you search for UIVviewController in XCode docs, also read the lovely article: “Why won’t my UIViewController rotate with the device”.
Common Solutions
In my experience, using several view controllers and adding their views to the main window is error prone. Pity, because it feels like a natural, no-nonsense approach to displaying views (but see the nice solution, below).
- Use a unique view controller. Doesn’t sound great but if you have a simple app, at least it’s safe.
- Display the view controller’s view modally. This often makes sense since a view controller is meant to manage one screen’s worth of content. IMHO the downside is that system semantics are associated with modal transitions, and misinterpreting how these transitions should be used may hurt you. For example, you may be struck by lightning.
- Use a navigation controller, a tab bar interface or a popover.
- Hacking a controller’s view down the hierarchy of the controller that receives orientation events. Ugly. However, note that hiding and showing views is a lot faster than adding and removing views. On older devices this may affect the user experience.
A nice solution
I wasn’t desperately happy with *any* of the above. Following the ‘one controller manages one screen’s worth of content’ idea, it would make sense if…
- APIs prevented us from adding several views to the main window.
- Any view added to the main window must be managed by a view controller.
- We could actually tell the window which controller it should be working with.
In IOS4.0 and later, there is a public, documented property that works with UIWindow. You can assign a window’s rootViewController property. This does a lot of work underhand:
- Remove the existing view hierarchy displayed by the window
- Add the new root controller’s view hierarchy to the window
- Make the VC ‘frontmost’. That VC will now receive notifications when the orientation changes.
I tried this and it works like a charm. I seemed less lucky when combining [window addSubview:] with the root view controller property, but maybe it can still work.
Resizing OpenGL surfaces
EAGLView is a utility class for displaying OpenGL content. You can find it in iOS SDK samples and it is often used as a starting point for OpenGL applications.
When an instance of EAGLView is rotated, it calls its [layoutSubviews] method. If you created your EAGLView programmatically, remember to set the autoresizing mask, as follows:
view.autoresizingMask=UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
Otherwise your EAGLView won’t resize, and [layoutSubviews] is never called.
layoutSubviews calls this onto another utility class, aka ‘renderer’ (typically, ES1/ES2Renderer).
[renderer resizeFromLayer: (CAEAGLLayer*) self.layer];
The sample code provided in ES1/ES2Renderer resizes the underlying buffers correctly, so you could use it as a starting point. I get wimpy when it comes to buffers, but I still managed to get it right using the following ideas:
- The color buffer is resized from the CAEAGLLayer.
- We then retrieve the backing width/height from that same buffer.
- Other buffers (depth, MSAA etc…) can be reallocated using whatever method we already know (remember to use the updated width/height).


Comments