External Display Support versus Stage Manager on iPadOS 16
Apple have provided support for connecting external displays to iOS devices for many years with their Lightning Digital AV Adapter and, for modern iPads with a USB-C connector, USB-C Digital AV Multiport Adapter. A full-screen non-interactive view controller can be displayed on an external display whenever the app is running in the foreground on the main device. This feature isn’t useful for many types of apps. Even when it could be useful, few apps go to the trouble of supporting it.
This article gives a brief overview of how apps can support an external display and then discusses a possibly undesirable interaction with Stage Manager on iPadOS 16 and possible workarounds.
Prior to iOS 13,
UIScreen notifications were used to inform the app when an external display had been connected or disconnected. The app could create a new view controller and window attached to the external screen. This mechanism should not be used unless you need to support an external display earlier than iOS 13. If you need to do that, see Jordan Morgan’s tutorial on Supporting External Displays. In an article from 2018, I also wrote about the old API and why I felt it was useful to support an external display in some of my own apps: External Display Support on iOS.
The scene lifecycle added in iOS 13 brought support for multiple windows on iPad. The
Application Scene Manifest section of the
Info.plist declares whether the app can support multiple windows. It can also list one or more window scene configurations. There are separate sessions defined for the application and an external display. The app delegate method
AppDelegate.application(_:configurationForConnecting:options:) is used in conjunction with the information in the
Info.plist or can programmatically create its own scene configurations. See Apple’s article Presenting content on a connected display for more details.
Then came Stage Manager on iPadOS 16. This dramatically increases the functionality of an external display connected to iPad models that support it (those with an M1 or later processor). The application window(s) for an app can be open on the internal or external display, but not both at the same time.
At the time of writing, Stage Manager support is still in beta and, frankly, quite buggy. Apple have changed the terminology for the previous external display support and now refers to it as “legacy” to distinguish it from Stage Manager.
The (Potential) Problem
On iPhone and iPad before Stage Manager, an external display will mirror the device unless the foreground app has explicit support for a custom view to show on the external display. A mirrored interface will be letter-boxed or pillar-boxed to fill the display without changing the aspect ratio. For example, a portrait iPhone appears tall and thin with black borders to the left and right when mirrored on a landscape 16:9 display.
With Stage Manager, an external display will show the Stage Manager interface whether Stage Manager is enabled or not on the internal display (but only when the iPad is docked in the Magic Keyboard at the time the external display is connected, it seems). However, when running an app on the internal display that supports the legacy external display feature, iPadOS 16 displays the full-screen non-interactive view on the external display. This keeps backward compatibility, but prevents Stage Manager from being used on the external display whenever such an app is running in the foreground on the iPad.
Apps which took advantage of the external display in the past are potentially worse citizens on iPadOS 16 because they override Stage Manager on the external display. I think it would be better if an app that supports the external display API did not prevent Stage Manager from being used on an external display.
iPadOS 16.1 disable Stage Manager support for external displays because it was not yet ready to be released. iPadOS 16.2 betas re-enabled it. On 16.2 beta 2 (and maybe beta 1), simply launching an app with an external window scene on the iPad would cause an instant Springboard crash. This seems to have been fixed in 16.2 beta 3.
The following screenshots show the
Info.plist for a sample app that accompanies this article. It uses storyboards for the window scenes, and supports multiple windows and an external display. Both the property list and the underlying XML source are shown.
Note that the
UIWindowSceneSessionRoleExternalDisplay is the key that was used prior to iOS/iPadOS 16. In the property list view, Xcode 14 labels this as “External Display Session Role (Legacy)”. For apps that need to keep supporting the external display prior to iOS/iPadOS 16 this older name must be used.
There is a new key
UIWindowSceneSessionRoleExternalDisplayNonInteractive which Xcode 14 labels as “External Display Session Role Non-Interactive”. This can be used for an app which requires iOS 16. Apps run on iOS/iPadOS 16 will look for this new key and fall back to the old key if it is absent.
I’m going to present a series of potential solutions to the problem of blocking Stage Manager on the external display when running an app on the internal display that supports an external display. They vary in their severity and flexibility.
Stop Supporting Legacy External Display APIs
The simplest solution to this problem is to simply stop supporting the legacy external display API by removing the
Info.plist keys associated with the external display.
This is the nuclear approach and removes existing functionality from your users, including those who aren’t using or can’t use Stage Manager. They may not even using iOS/iPadOS 16.
If this approach is acceptable, the supporting code for the external display can also be removed (but it doesn’t have to be — it’s benign and unused once the
Info.plist keys are removed).
Stop Supporting iOS/iPadOS 15 and Earlier
A slightly less extreme solution is to stop supporting external displays as above and also increase the minimum deployment version of your app to iOS 16. This is subtly different to the first solution as it allows users who do not upgrade to iOS/iPadOS 16 to continue using an older version of your app that does support an external display. However, users of the older version of the app are now effectively abandoned. This is probably not a viable solution for most apps.
Stop Supporting External Displays on iOS/iPadOS 16
A less extreme solution is to continue to support the legacy external display API prior to iOS/iPadOS 16 but to not support it on iOS/iPadOS 16. Since the new
Info.plist key is checked first on iOS/iPadOS 16 it is possible to add an entry under the new name which contains an empty configuration.
Note that there needs to be an array beneath the new key otherwise a crash occurs when connecting an external display. It doesn’t matter if the array contains an empty dictionary or not. Using Xcode 14’s property list editor to remove the leaf nodes inside the first item in the array results in the following XML source (leaving an array containing an empty dictionary):
The empty array of scene definitions under the new key prevents iOS/iPadOS 16 from instantiating a window when an external display is connected. This keeps Stage Manager visible on the external display when the app is open on the internal display. Older versions of iOS/iPadOS look for the old key and will continue to support the legacy external display API.
A disadvantage of this solution is that it also prevents the external display from being used on iPhone running iOS 16 and iPads running iPadOS 16 that don’t support Stage Manager.
Support External Display at Runtime
The best solution, in my opinion, is to make a runtime decision whether to support the legacy external display API or not. In my own apps that have external display support, I am adding an ‘External Support’ setting to control this. It defaults to enabled to preserve backwards compatibility except on iPadOS 16 where I think it is more desirable to not block Stage Manager on an external display. I don’t try to distinguish between iPad devices that can or cannot support Stage Manager. Since it is a setting, the user can choose to keep the old behaviour even on iPadOS 16 if they really want. It also allows the user to choose to mirror their display on older iOS versions if they don’t want the custom external display view for some reason.
How can we programmatically choose whether to support an external display? The solution lies in not providing a window for iOS/iPadOS to use on the external display.
For an app which uses a storyboard to define the external window scene, we can conditionally remove the storyboard in the App Delegate method when configuring the scene for an external display.
This feels like even more of a hack than the empty
Info.plist array, but it works on iOS 13 through 16 on iPhone and iPad.
The sample app that accompanies this article demonstrates this solution. It has a simple user interface which shows different coloured views for application and external windows. This makes it easy to distinguish between a mirrored interface and the custom external display view.
You will need to add your own Team in the Xcode project settings to run the sample app on a real device. An external display can be simulated with the iOS/iPadOS simulator but will be black unless a custom external display interface is available. The simulated display does not mirror the iPhone or iPad display. Use the I/O -> External Displays menu in the iOS simulator to add/remove a simulated external display. The release notes for Xcode 14.1 claim Stage Manager can be enabled in the simulator but I have been unable to get it to work.
The application window has a switch for toggling a UserDefaults setting that controls whether external display support should be enabled.
Due to the way that iOS/iPadOS caches scene configurations, a change to the app setting does not immediately affect the external display. The setting is only used when connecting an external display. After changing the setting, the user must disconnect and reconnect the external display to see a change in behaviour. Alternatively, killing the app from the task switcher and relaunching it will also re-create the external display scene.
If you run the sample app yourself in Xcode, be aware that changes to user defaults are not saved immediately. You need to return to the home screen before killing the debug session to ensure a change to the setting has been persisted to user defaults.
When turning the switch off, I tried to iterate over connected window scenes and destroy the one with an external role. Prior to iOS 16 this didn’t do anything (the external display remained visible). On the version of iPadOS 16.1 included with Xcode 14.1 beta 2 it just crashed Springboard (or one of the boards).
Adding support for external displays is reasonably straight forward, especially using the scene lifecycle introduced in iOS 13. The Stage Manager interface is a very different paradigm and isn’t really a good fit with the legacy external display API. I guess that’s why it’s called “legacy”.
I can understand why Apple chose to keep supporting the legacy mode, but I think most Stage Manager users would be surprised, perhaps even frustrated, if Stage Manager was not available on an external display when running certain apps on the iPad internal display. Apps which went the extra step to provide that support in the past are now effectively penalised and result in a worse user experience. The solutions discussed in this article explain how an app developer can remove support for the legacy external display or, with a bit more effort, allow the user to choose between the old and new worlds.
Other Articles That You Might Like
The new iPhone 14 Pro and Pro Max devices introduce new screen resolutions. See my article How iOS Apps Adapt to the various iPhone 14 Screen Sizes for information.
I have written a whole series of articles explaining the changes that have occurred to SF Symbols since their introduction in iOS 13. The most recent, SF Symbol Changes in iOS 16.0, has links to all the earlier articles.
I have also written articles on View Controller Presentation Changes in iOS and iPadOS 16, Xcode Build Times with Custom SF Symbols, How iPad Apps Adapt to the New 8.3" iPad Mini, How iOS Apps Adapt to the various iPhone 12 Screen Sizes, Bringing Adaptivity to Mac Catalyst, How to Switch Your iOS App and Scene Delegates for Improved Testing, View Controller Presentation Changes in iOS 13 and how to Hide Sensitive Information in the iOS App Switcher Snapshot Image.
The search algorithm used in Adaptivity’s System Colors and System Images views is described in A Simple, Smart Search Algorithm for iOS in Swift.
If you found any of these articles helpful then please take a look at my apps in the iOS App Store to see if there’s anything that you’d like to download (especially the paid ones 😀).
If you work with a lot of Xcode projects you might like my Mac Menu Bar utility XcLauncher. It’s like having browser bookmarks for your favorite Xcode projects, workspaces, playgrounds, and Swift packages. There is more information on my website about XcLauncher’s features.