View Controller Presentation Changes in iOS 13

Geoff Hackworth
Jun 7 · 14 min read

Introduction

At the time of writing, WWDC 2019 is drawing to a close. Like many iOS developers I’m slowly processing all the new information that Apple threw at us and will be trying to watch as many videos as I can in the coming weeks (and months!)

I have three immediate goals for my own apps:

  1. Do my current apps run without issues on iOS 13? Apple have a long history of backward compatibility based upon the version of Xcode the app was built with. History suggests that Xcode 10-built apps run on iOS 13 will behave as if they were run on iOS 12. But that’s not always the case.
  2. Do my apps work when built with Xcode 11 / iOS 13? Building with the latest tools opts app into new behaviour, bypassing backward-compatibility with previous iOS versions. Did anything break?
  3. What changes could/should I make to my apps to work better or take advantages of new iOS 13 features? This is the biggest task and will take the most time to investigate and implement. That’s a story for another day.

I’ve not yet installed iOS 13 on a real device but I can do some testing for #1 by installing Xcode-10-built apps into the iOS 13 simulator. My post iPad Navigation Bar and Toolbar Height Changes in iOS 12 explains the process in the Wait, What Did You Say? section. (That post was written a year ago so talks about Xcode 9 builds on iOS 12, but the process is the same.)

I’m still working on #2 but based on my initial testing and reading tweets from other developers making similar discoveries, I’ve found a number of behavioural changes to my apps when building with Xcode 11. I have a lot of videos to watch and information to assimilate, but in this post I want to focus on immediately noticeable, and potentially breaking, changes to view controller presentations in iOS 13.

Default Modal Presentation Style Change

The default presentation is now page sheet, not full screen. The header documentation for modalPresentationStyle says:

Defaults to UIModalPresentationAutomatic on iOS starting in iOS 13.0, and UIModalPresentationFullScreen on previous versions.

By default UIViewController resolves UIModalPresentationAutomatic to UIModalPresentationPageSheet, but other system-provided view controllers may resolve UIModalPresentationAutomatic to other concrete presentation styles.

The consequences of this change differ on iPhone and iPad.

Modal Presentations on iPhone

A form sheet, page sheet or popover presentation on iPhone is adapted to full screen unless a UIAdaptivePresentationControllerDelegatemethod is used to prevent the adaption. As an example, a Settings screen might be presented as a form sheet so that it appears full screen on iPhone and in the smaller form sheet size on iPad. Technically, the appearance/adaption depends on the horizontal size class. Form/page/popover presentations on Landscape iPhone Plus and XS Max devices don’t fill the screen because they are regular width. iPad appearance depends on slide over and split-screen multitasking size. It can change whilst a view controller is presented if the horizontal size class changes because the user adjusts or removes the split.

The following screenshots show a form sheet presentation on iPhone XS for three cases: Xcode 10 build run on iOS 12, Xcode 10 build run on iOS 13, and Xcode 11 build run on iOS 13.

The iOS 12 backward compatibility of iOS 13 for the Xcode 10 build results in a full screen presentation. The style of grouped table views has changed in iOS 13 to hide the space above the first section when there’s no header. Even the Xcode 10/iOS 12 build behaves differently when run on iOS 13, which is not what I expected.

The biggest change in iOS 13 is, of course, that card-like appearance 😍. The presenting view controller has been shrunk in size and its very top is still slightly visible behind the newly-presented view controller. The window behind the root view controller is also slightly visible behind the presenting view controller and underneath the status bar (which has automatically changed color — nice!) A default black window background looks great, especially on devices with a notch. Some of my apps were setting the window background to white (for reasons lost to the midst of time) and that looked pretty ugly. I quickly fixed that!

View Controllers all the Way Down

If the presented view controller presents yet another view controller (and so on), the cards stack up with a nice animation. Note that only the top-most presented view controller and its presenting view controller can be seen (the presenting presenting view controller is no longer visible):

Another potentially important difference in behaviour is what happens to the presenting view controller. A full screen presentation that fully covers the view controller will result in the presenting view controller being removed from the view hierarchy (an over full screen presentation prevents that from happening). Of course, the presenting view controller has to remain in the view hierarchy with the new card-like presentation because it is still visible. However, even though the user can only see two view controllers at once, repeatedly presenting view controllers does not remove the lower view controllers from the view hierarchy.

Size Changes

The new card-style appearance means that the presented view controller is not quite as tall on iOS 13 as it was on iOS 12:

I Want my Full Screen!

Explicitly asking for a full screen modal presentation will override this new behaviour. However, that might break your iPad behaviour (if you want a form or page sheet there). Please resist the temptation to check the device idiom and use a different presentation style for iPhone and iPad. If the last few years have taught us anything, it’s that we shouldn’t be making assumptions based on device types or screen sizes. If you want a form/page sheet appearance on iPad but full screen on iPhone you should be able to useUIAdaptivePresentationControllerDelegate to adapt to full screen in compact width environments (including the smaller iPad multitasking sizes).

Modal Presentations on iPad

Form Sheets

Form sheets presented on iPad are unchanged in iOS 13:

Page Sheets

As noted above, the default modal presentation style on iOS 13 is now page sheet. On iPad the page sheet size has changed, in both portrait and landscape:

Adaptivity presenting a page sheet on 11" iPad Pro with default (Large) content size category: Xcode 10 build on iOS12, and Xcode 11 build on iOS 13

As on iOS 12, the readable content guide changes size as the content size category changes. The actual size seems to be different on iOS 12 and 13 at some content size categories.

The page sheet itself also gets larger on iOS 13 as the content size category increases. Here’s the Extra Extra Extra Large content size category (the largest size available without enabling the larger accessibility sizes):

Adaptivity presenting a page sheet on 11" iPad Pro with ExtraExtraExtraLarge content size category: Xcode 10 build on iOS12, and Xcode 11 build on iOS 13

Other Presentations

Remember the header documentation for modalPresentationStyle:

By default UIViewController resolves UIModalPresentationAutomatic to UIModalPresentationPageSheet, but other system-provided view controllers may resolve UIModalPresentationAutomatic to other concrete presentation styles.

I’m not 100% sure of all the rules for “other system-provided view controllers” but I did find that presenting a split screen view controller without setting a modal presentation style gives a full-width card-like appearance:

Presenting a split screen view controller is not something most apps would do, but it is one of the views in my Adaptivity app. I’ve updated my code to explicitly present the split screen view controller full screen as most apps that use a split view controller would use it as the root view controller.

Swipe to Dismiss

Another important change that affects both iPhone and iPad is that non-full-screen modal presentations (except popovers) can be interactively dismissed with a downward swipe. The presenting view controller animates back up to full screen:

Note that in this example, I had pushed an About screen onto my modally-presented Settings screen’s navigation controller. Even though the navigation controller was not showing its root view controller, the interactive dismissal is still possible.

Don’t Swipe me Bro!

This change is especially important for apps that push truly “modal” view controllers (either directly or onto the stack of a modally-presented navigation controller). If you are relying on the user tapping a Done button (or similar) or navigating back to the top of a navigation controller stack in order to dismiss a modally-presented view controller, the new swipe-to-dismiss behaviour might break your app because your dismiss button handler will not be executed.

For example in my Pomodoro Timer app Pommie, the user can navigate to a sub-screen in the Settings screen and add or edit a timer profile (a configuration for the work/break periods for a specific kind of task):

In the case of Pommie, I think it’s OK (and safe) if the user dismisses the entire Settings view controller away with a swipe from either the top screen or the Timer Profiles sub-screen (and most of the other sub-screens). Users will probably come to expect that they can dismiss with a swipe and I want my apps to behave like good iOS 13 citizens. However, I feel that I should prevent that happening in the Add/Edit Timer Profile screen because there is a risk of losing changes. Would the user expect changes to be abandoned or saved if they swiped to dismiss? It’s not clear what they intended.

One part of fixing this issue is a new view controller property: isModalInPresentation. From the header documentation:

modalInPresentation is set on the view controller when you wish to force the presentation hosting the view controller into modal behavior. When this is active, the presentation will prevent interactive dismiss and ignore events outside of the presented view controller’s bounds until this is set to NO.

To get iOS 12-like behaviour for my Settings screen on iOS 13, I could just set isModalInPresentation to true for the modally-presented navigation controller. If the user tries to swipe down to dismiss, the view controller moves slightly but resists the user’s action and cannot be dismissed.

The property can be changed at any time so you could, for example, allow dismissal if the user has not yet made any changes that would be lost unless they explicitly saved them. But as soon as a change has been made (perhaps when you then enable a Save button), you can set isModalInPresentation to prevent dismissal with a swipe. This will then force the user to be explicit and tap either the Cancel or Save button. Alternatively, you can detect the failed attempt to dismiss and present an action sheet asking them whether to abandon or save the change (see the next section for more details).

Apple have been smart: setting isModalInPresentation on my Add/Edit Timer Profile view controller will prevent the Settings screen being dismissed with a swipe whilst that view controller is shown. As soon as I Cancel or Save and pop back up a level in the stack, the interactive swipe is enabled again. In fact, they’ve been really smart: interactive dismissal is disabled if any of the view controllers on the navigation stack has isModalInPresentation set to true. This means if my Add Timer Profile screen or a smilar modal screen were to push any further sub-screens on the stack, they would not need to set isModalInPresentation to prevent dismissal. The presence of a view controller higher up the stack that does set isModalInPresentation will prevent dismissal.

Except, when it doesn’t! In my testing, if a view controller has a search bar, the isModalInPresentation property on the search controller takes precedence when the search bar is active. That is, it becomes possible to swipe to dismiss during a search even if the view controller containing the search bar has set isModalInPresentation to true. This might be a bug in the first beta, but is probably related to how search bars present the search controller. Depending on your app’s requirements you can either:

  • set isModalInPresentation directly on the navigation controller that was presented modally (rather than on all the individual view controllers you can show in the stack)
  • set isModalInPresentation on the search controller itself so when it is presented when the user taps on the search bar, it will control whether interactive dismissal is possible.

Detecting Dismissal

As noted earlier, some apps might need to execute some code when a modally presented view controller is dismissed using a Cancel, Done or Save button (other than just dismissing it). For example, you might need to restart a timer in a game, or act upon some information that the user changed in the presented view controller. That code won’t be executed if the user dismisses with a swipe. Your button isn’t pressed, so its action handler won’t be called. This could break the behaviour of your app.

The simplest way to avoid this problem is to prevent the interactive dismissal using isModalInPresentation. The user will have to tap a button to dismiss the view controller, just as they did before iOS 13. There is another way…

iOS 13 adds some new UIAdaptivePresentationControllerDelegate methods. These allow another object (typically the presenting view controller) to control whether the interactive dismissal should be allowed (an alternative to using isModalInPresentation), and to be informed when the interactive dismissal begins or completes. These methods are well-documented and clearly explained in WWDC 2019 224: Modernizing Your UI for IOS 13 starting at 15 minutes. Note that presentationControllerWillDismiss can be called multiple times if the user starts swiping to dismiss, changes their mind and then swipes again. The presentationControllerDidDismiss method is where you need to execute the extra code that currently occurs when a Cancel, Done or Save button is pressed (of course, you don’t need to dismiss the presented view controller). These methods won’t be called if the view controller is dismissed programmatically. Therefore you will still need to execute your code in the button handler (or your own delegate) that triggers the dismissal, even when running on ios 13.

The presentationControllerDidAttemptToDismiss delegate method is interesting. It will be called if the user tries to swipe to dismiss but isModalInPresentation resulted in the dismissal being blocked. The WWDC video suggests showing an action sheet (which will be a popover on iPad) asking if the user wants to abandon or save their changes. This seems like a really good idea if the presented view controller has Cancel and Save/Done buttons: creating a new note, editing the properties for an object etc.

For a nested view controller on a navigation stack with Cancel and Save buttons (for example, the Timer Profiles screen inside Pommie’s Settings screen), I think it’s more complicated. The code for performing the save is probably in the view controller one level higher in the stack (the delegate of the top view controller), and not in the object that would be the UIAdaptivePresentationControllerDelegate. Trying to route the user’s choice to the object that can perform the save might be pretty messy. In my own apps, I think I will just block the dismissal in view controllers that require an explicit cancel/save action if they are not at the top of a navigation stack.

Resources

The WWDC 2019 videos will be the best place to discover what has changed in iOS 13, what changes you need to make to your apps to keep them working correctly when built on Xcode 11, and what changes you could make to improve them to take advantage of new features. Here’s a few to start with:

Conclusion

So far, I’ve not found any issues with my Xcode 10-built apps running on iOS 13. Apple’s backwards-compatibility really helps here. I was a little surprised to see the change in appearance of the grouped table view.

The Xcode 11 builds needed some small fixes to deal with the changes in modal presentations discussed in this post. There will probably be more changes I’ve not yet discovered, and that’s before I start making changes to take advantage of new iOS 13 features.

Test your modal presentations carefully (especially with search bars) and swipe downwards to see what happens in your own apps! Decide whether you want to allow the user to swipe to dismiss modally presented view controllers, perhaps on a screen-by-screen basis in a navigation stack, and use isModalInPresentation to get the behaviour you need to prevent accidental data loss from an errant swipe. For more flexibility and control, use UIAdaptivePresentationControllerDelegate.

Other Articles That You Might Like

If you’re an iOS developer you might be interested in my long-running series of articles that show how apps adapt to newer device sizes depending on which Xcode version they are built with:

Most of the screenshots in those articles were taken from the iOS simulator running my Adaptivity iOS app. There is an iPhone-only version of the app to show How iPhone-only Apps Appear on iPad (it changed in iOS 12).

You may not have realised that there were iPad Navigation Bar and Toolbar Height Changes in iOS 12. Or maybe you want to Fix Xcode’s iPhone XS, XS Max and XR Simulator Names and iOS Versions.

I have also written about External Display Support on iOS and Working with Multiple Versions of Xcode. The latter is particularly useful during the time between WWDC and the public release of iOS in September.

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.

Geoff Hackworth

Written by

Independent and freelance software developer for iPhone, iPad and Mac