Presenting Content on an External Display

If you use AirPlay to allow users to redirect content to an external device display, you can either mirror the current app content on the second display or show different content on it.

You can enhance the user’s experience by presenting different content on each display. For example, you might present your app UI on the host device’s display while at the same time you show high-definition media content on the external display.

To show separate content on an external device display, you follow this basic process:

  1. At app startup, check for the presence of an external display and register for the screen connection and disconnection notifications.

  2. When an external display is available—whether at app launch or while your app is running—create and configure a window for it.

  3. Associate the window with the appropriate screen object, show the second window, and update it normally.

To re-enable mirroring after displaying unique content, simply remove the window you created from the appropriate screen object.

Create a New Window If an External Display Is Already Present

You can check for the presence of a second display when your app launches by testing the screens array of the window’s UIScreen object. Typically, the screens array contains only one screen object because this object represents the display of the host iOS-based device. If the user connects to an external device, the array includes an additional screen object that represents the external display. iOS-based devices that support connecting to an external display include iPhone and iPod touch devices that have Retina displays and iPad. Older devices—such as iPhone 3GS—do not support external displays.

Listing 2-1 shows a method that checks for the presence of an external display at app startup. If a second display is available, it creates a window for it.

Listing 2-1  Checking for the presence of an external display

- (void)checkForExistingScreenAndInitializeIfPresent
{
    if ([[UIScreen screens] count] > 1)
    {
        // Get the screen object that represents the external display.
        UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];
        // Get the screen's bounds so that you can create a window of the correct size.
        CGRect screenBounds = secondScreen.bounds;
 
        self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
        self.secondWindow.screen = secondScreen;
 
        // Set up initial content to display...
        // Show the window.
        self.secondWindow.hidden = NO;
    }
}

Register for Connection and Disconnection Notifications

When the user connects or disconnects a display, the system sends appropriate notifications to your app. These kinds of notifications are crucial for handling changes to external displays gracefully. You should use these notifications to update your app state and create or destroy the window associated with the external display.

The important thing to remember about the connection and disconnection notifications is that they can come at any time, even when your app is suspended in the background. Because you can’t predict the arrival of these notifications, it is best to observe them from an object that is going to exist for the duration of your app’s runtime, such as your app delegate. If your app is suspended, the notifications are queued until your app exits the suspended state and starts running in either the foreground or the background.

Listing 2-2 shows one way to register for these notifications. (The code for handling these notifications is shown in Handle Connection and Disconnection Notifications.)

Listing 2-2  Registering for screen connection and disconnection notifications

- (void)setUpScreenConnectionNotificationHandlers
{
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
 
    [center addObserver:self selector:@selector(handleScreenDidConnectNotification:)
            name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleScreenDidDisconnectNotification:)
            name:UIScreenDidDisconnectNotification object:nil];
}
 

Handle Connection and Disconnection Notifications

The notification object you receive in your custom notification handlers is the screen object that represents the external display. As you did at app startup, you need to determine the bounds of the new screen so that you can create and initialize a window of the correct size. Then set the window’s screen property to the new screen object and set the window’s hidden property to NO, as shown in Listing 2-3.

Listing 2-3  Handling screen connection and disconnection notifications

- (void)handleScreenDidConnectNotification:(NSNotification*)aNotification
{
    UIScreen *newScreen = [aNotification object];
    CGRect screenBounds = newScreen.bounds;
 
    if (!self.secondWindow)
    {
        self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
        self.secondWindow.screen = newScreen;
 
        // Set the initial UI for the window.
    }
}
 
- (void)handleScreenDidDisconnectNotification:(NSNotification*)aNotification
{
    if (self.secondWindow)
    {
        // Hide and then delete the window.
        self.secondWindow.hidden = YES;
        self.secondWindow = nil;
 
    }
 
}

Accommodate the Attributes of an External Display, if Necessary

Many displays support multiple resolutions, some of which use different pixel aspect ratios. A UIScreen object uses the display’s preferred screen mode by default and it’s best to use this mode as much as possible. You can get a display’s preferred mode in the UIScreen object’s preferredMode property.

Note that—regardless of screen mode—your app needs to handle the performance implications of using one device’s resources to generate a second screen’s worth of content. For example, you might have to test to discover an optimum frame rate that allows you to render content on both screens without dropped frames or stuttering.

In some cases, you might want to change a display’s screen mode to one that is more suitable for your content before associating your window with it. For example, if you are implementing a game using OpenGL ES and your textures are designed for a 640 x 480 pixel screen, you might change the screen mode for displays with higher default resolutions. Before you change the screen mode, make sure that the user experience is still acceptable if your content gets scaled up.

If you plan to use a screen mode other than the default one, you should apply that mode to the UIScreen object before associating the screen with a window. The UIScreenMode class defines the attributes of a single screen mode. You can get a list of the modes supported by a screen object from its availableModes property and iterate through the list for one that matches your needs.

For more information about screen modes, see UIScreenMode Class Reference.

In addition to including the screen mode, a UIScreen object includes the overscanCompensation property you can use to adjust the overscan compensation for an external display, if necessary. Overscanning refers to a practice that originated with cathode ray tube displays. Because of technical limitations, old CRTs scanned the incoming picture past the edges of the tube and displayed a cropped image. Although the limitations have been fixed, many broadcasters and display manufactures continue to expect overscanning. Using the default value of the overscanCompensation property—that is, UIScreenOverscanCompensationScale—iOS scales your content appropriately when it detects overscanning in an external display.

In rare cases, you might want to use a different value for the overscanCompensation property, but doing so always results in more work that you have to do. For example, if you use UIScreenOverscanCompensationInsetBounds, you must be prepared to handle the bounds of nonstandard display sizes.