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.

../Art/view_orientations_2x.png

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:

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 supportedInterfaceOrientations method

- (NSUInteger)supportedInterfaceOrientations
{
    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 setStatusBarOrientation:animated: method).

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 preferredInterfaceOrientationForPresentation method

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return UIInterfaceOrientationLandscapeLeft;
}

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 UIApplicationInvalidInterfaceOrientationException exception.

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:

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 shouldAutorotateToInterfaceOrientation: method

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
{
   if ((orientation == UIInterfaceOrientationPortrait) ||
       (orientation == UIInterfaceOrientationLandscapeLeft))
      return YES;
 
   return NO;
}

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:

  1. The window calls the root view controller’s willRotateToInterfaceOrientation:duration: method.

    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.

  2. The window adjusts the bounds of the view controller’s view. This causes the view to layout its subviews, triggering the view controller’s viewWillLayoutSubviews method. When this method runs, you can query the app object’s statusBarOrientation property to determine the current user interface layout.

    See “How View Controllers Participate in the View Layout Process.”

  3. 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.

  4. The animation is executed.

  5. The window calls the view controller’s didRotateFromInterfaceOrientation: method.

    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.

Figure 8-1  Processing an interface rotation

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:

  1. Your view controller presents another view controller’s contents full screen.

  2. The user rotates the device so that the user interface orientation changes.

  3. 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:

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

@implementation PortraitViewController
- (void)awakeFromNib
{
    isShowingLandscapeView = NO;
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                 selector:@selector(orientationChanged:)
                                 name:UIDeviceOrientationDidChangeNotification
                                 object:nil];
}
 
- (void)orientationChanged:(NSNotification *)notification
{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
        !isShowingLandscapeView)
    {
        [self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
        isShowingLandscapeView = YES;
    }
    else if (UIDeviceOrientationIsPortrait(deviceOrientation) &&
             isShowingLandscapeView)
    {
        [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.