Supporting Multiple Interface Orientations
The accelerometers in iOS–based devices make it possible to determine the current orientation of the device. By default, an app supports both portrait and landscape orientations. When the orientation of an iOS–based device changes, the system sends out a
UIDeviceOrientationDidChangeNotification notification to let any interested parties know that the change occurred. By default, the UIKit framework listens for this notification and uses it to update your interface orientation automatically. This means that, with only a few exceptions, you should not need to handle this notification at all.
When the user interface rotates, the window is resized to match the new orientation. The window adjusts the frame of its root view controller to match the new size, and this size in turn is propagated down the view hierarchy to other views. Thus, the simplest way to support multiple orientations in your view controller is to configure its view hierarchy so that the positions of subviews are updated whenever its root view’s frame changes. In most cases, you already need this behavior because other conditions may cause the view controller’s visible area to change. For more information on configuring your view layout, see “Resizing the View Controller’s Views.”
If the default behavior is not what you want for your app, you can take control over:
The orientations supported by your app.
How a rotation between two orientations is animated onscreen.
View controllers that do not fill the screen usually should not care about the orientation of the user interface. Instead, fill the area provided by the parent view controller. A root view controller (or a view controller presented full screen) is more likely to be interested in the orientation of the device.
Controlling What Interface Orientations Are Supported (iOS 6)
When UIKit receives an orientation notification, it uses the
UIApplication object and the root view controller to determine whether the new orientation is allowed. If both objects agree that the new orientation is supported, then the user interface is rotated to the new orientation. Otherwise the device orientation is ignored.
When a view controller is presented over the root view controller, the system behavior changes in two ways. First, the presented view controller is used instead of the root view controller when determining whether an orientation is supported. Second, the presented view controller can also provide a preferred orientation. If the view controller is presented full screen, the user interface is presented in the preferred orientation. The user is expected to see that the orientation is different from the device orientation and rotate the device. A preferred orientation is most often used when the content must be presented in the new orientation.
Declaring a View Controller’s Supported Interface Orientations
A view controller that acts as the root view controller of the main window or is presented full screen on the main window can declare what orientations it supports. It does this by overriding the
supportedInterfaceOrientations method. By default, view controllers on devices that use the iPad idiom support all four orientations. On devices that use the iPhone idiom, all interface orientations but upside-down portrait are supported.
You should always choose the orientations your view supports at design time and implement your code with those orientations in mind. There is no benefit to choosing which orientations you want to support dynamically based on runtime information. Even if your app did this, you would still have to implement the necessary code to support all possible orientations, so you might as well just choose to support the orientation or not up front.
Listing 8-1 shows a fairly typical implementation of the
supportedInterfaceOrientations method for a view controller that supports the portrait orientation and the landscape-left orientation. Your own implementation of this method should be just as simple.
Listing 8-1 Implementing the
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
Dynamically Controlling Whether Rotation Occurs
Sometimes you may want to dynamically disable automatic rotation. For example, you might do this when you want to suppress rotation completely for a short period of time. You must temporarily disable orientation changes you want to manually control the position of the status bar (such as when you call the
If you want to temporarily disable automatic rotation, avoid manipulating the orientation masks to do this. Instead, override the
shouldAutorotate method on the topmost view controller. This method is called before performing any autorotation. If it returns
NO, then the rotation is suppressed.
Declaring a Preferred Presentation Orientation
When a view controller is presented full-screen to show its content, sometimes the content appears best when viewed in a particular orientation in mind. If the content can only be displayed in that orientation, then you simply return that as the only orientation from your
supportedInterfaceOrientations method. If the view controller supports multiple orientations but appears better in a different orientation, you can provide a preferred orientation by overriding the
preferredInterfaceOrientationForPresentation method. Listing 8-2 shows an example used by a view controller whose content should be presented in landscape orientation. The preferred interface orientation must be one of the orientations supported by the view controller.
Listing 8-2 Implementing the
For more on presentation, see “Presenting View Controllers from Other View Controllers.”
Declaring the App’s Supported Interface Orientations
The easiest way to set an app’s app’s supported interface orientations is to edit the project’s
Info.plist file. As in the case of the view controller, you define which of the four interface orientations are permitted. For more information, see Information Property List Key Reference.
If you restrict the app’s supported orientations, then those restrictions apply globally to all of the app’s view controllers, even when your app uses system view controllers. At any given time, the mask of the topmost view controller is logically
ANDed with the app’s mask to determine what orientations are permitted. The result of this calculation must never be
0. If it is, the system throws a
Because the app’s mask is applied globally, use it sparingly.
Understanding the Rotation Process (iOS 5 and earlier)
On iOS 5 and earlier, a view controller can sometimes participate in the rotation process even when it isn’t the topmost full-screen view controller. This generally occurs when a container view controller asks its children for their supported interface orientations. In practice, the ability for children to override the parents is rarely useful. With that in mind, you should consider emulating the iOS 6 behavior as much as possible in an app that must also support iOS 5:
In a root view controller or a view controller that is presented full screen, choose a subset of interface orientations that make sense for your user interface.
In a child controller, support all the default resolutions by designing an adaptable view layout.
Declaring the Supported Interface Orientations
To declare your supported interface orientations, override the
shouldAutorotateToInterfaceOrientation: method and indicate which orientations your view supports. You should always choose the orientations your view supports at design time and implement your code with those orientations in mind. There is no benefit to choosing which orientations you want to support dynamically based on runtime information. Even if you did so, you would still have to implement the necessary code to support all possible orientations, and so you might as well just choose to support the orientation or not up front.
Listing 8-3 shows a fairly typical implementation of the
shouldAutorotateToInterfaceOrientation: method for a view controller that supports the default portrait orientation and the landscape-left orientation. Your own implementation of this method should be just as simple.
Listing 8-3 Implementing the
if ((orientation == UIInterfaceOrientationPortrait) ||
(orientation == UIInterfaceOrientationLandscapeLeft))
If your app supports both landscape orientations, you can use the
UIInterfaceOrientationIsLandscape macro as a shortcut, instead of explicitly comparing the orientation parameter against both landscape constants. The UIKit framework similarly defines a
UIInterfaceOrientationIsPortrait macro to identify both variants of the portrait orientation.
Responding to Orientation Changes in a Visible View Controller
When a rotation occurs, the view controllers play an integral part of the process. Visible view controllers are notified at various stages of the rotation to give them a chance to perform additional tasks. You might use these methods to hide or show views, reposition or resize views, or notify other parts of your app about the orientation change. Because your custom methods are called during the rotation operation, you should avoid performing any time-consuming operations there. You should also avoid replacing your entire view hierarchy with a new set of views. There are better ways to provide unique views for different orientations, such as presenting a new view controller (as described in “Creating an Alternate Landscape Interface”).
The rotation methods are sent to the root view controller. The root view controller passes these events on as necessary to its children, and so on down the view controller hierarchy. Here is the sequence of events that occur when a rotation is triggered:
The window calls the root view controller’s
Container view controllers forward this message on to the currently displayed content view controllers. You can override this method in your custom content view controllers to hide views or make other changes to your view layout before the interface is rotated.
The window adjusts the bounds of the view controller’s view. This causes the view to layout its subviews, triggering the view controller’s
viewWillLayoutSubviewsmethod. When this method runs, you can query the app object’s
statusBarOrientationproperty to determine the current user interface layout.
See “How View Controllers Participate in the View Layout Process.”
The view controller’s
willAnimateRotationToInterfaceOrientation:duration:method is called. This method is called from within an animation block so that any property changes you make are animated at the same time as other animations that comprise the rotation.
The animation is executed.
The window calls the view controller’s
Container view controllers forward this message to the currently displayed content view controllers. This action marks the end of the rotation process. You can use this method to show views, change the layout of views, or make other changes to your app.
Figure 8-1 shows a visual representation of the preceding steps. It also shows how the interface looks at various stages of the process.
Rotations May Occur When Your View Controller Is Hidden
If your view controller’s contents are not onscreen when a rotation occurs, then it does not see the list of rotation messages. For example, consider the following sequence of events:
Your view controller presents another view controller’s contents full screen.
The user rotates the device so that the user interface orientation changes.
Your app dismisses the presented view controller.
In this example, the presenting view controller was not visible when the rotation occurred, so it does not receive any rotation events. Instead, when it reappears, its views are simply resized and positioned using the normal view layout process. If your layout code needs to know the current orientation of the device, it can read the app object’s
statusBarOrientation property to determine the current orientation.
Creating an Alternate Landscape Interface
If you want to present the same data differently based on whether a device is in a portrait or landscape orientation, the way to do so is using two separate view controllers. One view controller should manage the display of the data in the primary orientation (typically portrait), while the other manages the display of the data in the alternate orientation. Using two view controllers is simpler and more efficient than making major changes to your view hierarchy each time the orientation changes. It allows each view controller to focus on the presentation of data in one orientation and to manage things accordingly. It also eliminates the need to litter your view controller code with conditional checks for the current orientation.
To support an alternate landscape interface, you must do the following:
Implement two view controller objects. One to present a portrait-only interface, and the other to present a landscape-only interface.
Register for the
UIDeviceOrientationDidChangeNotificationnotification. In your handler method, present or dismiss the alternate view controller based on the current device orientation.
Because view controllers normally manage orientation changes internally, you have to tell each view controller to display itself in one orientation only. The implementation of the primary view controller then needs to detect device orientation changes and present the alternate view controller when the appropriate orientation change occurs. The primary view controller dismisses the alternate view controller when the orientation returns to the primary orientation.
Listing 8-4 shows the key methods you need to implement in a primary view controller that supports a portrait orientation. When the primary view controller is loaded from the storyboard, it registers to receive orientation-changed notifications from the shared
UIDevice object. When such a notification arrives, the
orientationChanged: method then presents or dismisses the landscape view controller depending on the current orientation.
Listing 8-4 Presenting the landscape view controller
isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
- (void)orientationChanged:(NSNotification *)notification
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
[self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
isShowingLandscapeView = YES;
else if (UIDeviceOrientationIsPortrait(deviceOrientation) &&
[self dismissViewControllerAnimated:YES completion:nil];
isShowingLandscapeView = NO;
Tips for Implementing Your Rotation Code
Depending on the complexity of your views, you may need to write a lot of code to support rotations—or no code at all. When figuring out what you need to do, you can use the following tips as a guide for writing your code.
Disable event delivery temporarily during rotations. Disabling event delivery for your views prevents unwanted code from executing while an orientation change is in progress.
Store the visible map region. If your app contains a map view, save the visible map region value prior to beginning any rotations. When the rotations finish, use the saved value as needed to ensure that the displayed region is approximately the same as before.
For complex view hierarchies, replace your views with a snapshot image. If animating large numbers of views is causing performance issues, temporarily replace those views with an image view containing an image of the views instead. After the rotations are complete, reinstall your views and remove the image view.
Reload the contents of any visible tables after a rotation. Forcing a reload operation when the rotations are finished ensures that any new table rows exposed are filled appropriately.
Use rotation notifications to update your app’s state information. If your app uses the current orientation to determine how to present content, use the rotation methods of your view controller (or the corresponding device orientation notifications) to note those changes and make any necessary adjustments.