All Ruby on Rails Node JS Android iOS React Native Frontend Flutter QA

iOS 13 Modals Changes

Announcement

If you carefully listen to WWDC 2019 Platform State of the Union about 0:53:35 you might hear the following announcement:

Another component of iOS 13’s new look is cards. Since the original SDK, the default presentation style in iPhone has covered the full screen. We’re changing that default to, a much more fluid, card presentation. (…) and even better they are dismissible with just a single downwards swipe.

and then in WWDC 2019 Modernising Your UI for iOS 13 around 0:09:42 we can get quite a lot of information about cards:

(…) new in iOS 13 we have a standard design for presentations (…). For example, if I'm here in the contacts app and I tap plus to add a new contact, we have a new presentation style that looks like this, rather than the full screen presentation that we had before. You can see that the root view controller's view is not as scaled down and not removed from the view hierarchy.

Screenshot 2019-10-15 at 12.54.30

Surprise

The reason why this article came to live is that in iOS 13 the default value for UIViewController's modalPresentationStyle property is newly created .automatic . This allows system to automatically set correct presentation style for system provided view controllers. For example depending on whether you configure UIImagePickerController to use photo library or camera as a source, it will be shown as a page sheet or will take up full screen.

chenges in storyboards (1)

However, if you simply instantiate and present a custom subclass of UIViewController, system by default will resolve .automatic to .pageSheet. This results in the same code having way different effect on iOS 13 and any earlier version. Also, modals presented in new presentation style are dismissible, allowing users to disrupt designated program flow.

Page sheet presentation style

I thought that it’s a new presentation style, but it’s not. It is just a new design for already existing .pageSheet and very similar .formSheet . So for iPhone in portrait we get this card layout, however for iPhones in landscape we get full screen layout.

Screenshot 2019-10-15 at 14.30.50

It’s quite different on iPads though. Page sheet’s there are floating in the middle of screen. If we keep presenting new ones when some are already presented they will be stacked on top of each other. What is great about these on iPad is that their width follows the readable width (readableContentGuide), which makes them perfect for content worth a read!

chenges in storyboards (2)

Popover presentation style

Here we have another gotcha! If you set modalPresentationStyle of a view controller to .popover you will get a popover, right? Well, that is only true if you are in regular width. If you are in a compact width you will get a sheet. For a device size classes reference visit Apple Human Interface Guidelines on Adaptivity.

Pulling down

If you present something as a sheet the ability to pull it down is built-in. Apple will put a gesture recogniser on the entire presented view, so any non interactive are will trigger a pull down on a sheet. Also, if user pulls past the top in any of the scroll views or it’s subclasses, the sheet will be pulled down. This can become both, a blessing and a curse.

Thankfully Apple introduced two new APIs to control pulling down:

  • isModalInPresentation property of UIViewController - docs
  • UIAdaptivePresentationControllerDelegate protocool - docs

If you wish to prevent sheet from being dismissible, just set it’s isModalInPresentation to true. After being pulled down it will spring back in.

It’s out of scope of this article to go into details about pulling down and how to interactively handle it. I would like to strongly recommend watching this WWDC talk from 0:14:33 and taking a look at example code if you’re interested.

Appearance callbacks

Yet another gotcha.

Presentation type

Full screen

Page and Form Sheet

presenting VC

presented VC

presenting VC

presented VC

viewWillDisappear

viewWillAppear

 

viewWillAppear

viewDidDisappear

viewDidAppear

 

viewDidAppear

viewWillAppear

viewWillDisappear

 

viewWillDisappear

viewDidAppear

viewDidDisappear

 

viewDidDisappear

When using Sheet style the presenting ViewController is not removed from view hierarchy. Therefore, any appearance callbacks won’t be called. If you got any logic there, it is important to be aware of this and rework them.

When to use which presentation style

Based on Apple Human Interface Guidelines on Modality:

Firstly, we should evaluate whether or not we really need modal.

A modal experience takes people out of their current context and requires an action to dismiss, so it’s essential to use it only when it provides a clear benefit.

We don’t want to disturb or bother our users. But, when you do decide to use a modal our choice should be based on engagement level or complexity.

Use a sheet for non-immersive modal content that doesn’t enable a complex task.
Use a full-screen modal view for immersive content - such as videos, photos, or camera views - or a complex task that benefits from a full-screen presentation, such as marking up a document or editing a photo.

Eventually if you made up your mind, please consider and remember following:

  • Can you clearly tell what is a purpose of this modal? Keep modal tasks simple, short, and narrowly focused.
  • Modals always should include a dismiss button. This not only will it allow users that don’t know about pull gesture to dismiss, but also improve accessibility.
  • Regardless of whether people use a dismiss gesture or a button to close the view, if the action could result in the loss of user-generated data, present an action sheet that explains the situation and gives people ways to resolve it, like for example “Delete Draft“ and “Save Draft“ when dismissing new mail.
  • Generally it’s a good idea to add title to modal, which describes a purpose.

Quick fix

If you came here looking for a way to make popover behave the same way in iOS 13 as they did in earlier versions, here is what you are looking for:

chenges in storyboards

or in code:

let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true, completion: nil)

or you can swizzle a present method. Thanks to Abedalkareem Omreyh post on StackOverflow I can present you this neat extension which does exactly that:

extension UIViewController {
  static func swizzlePresent() {
    let orginalSelector = #selector(present(_: animated: completion:))
    let swizzledSelector = #selector(swizzledPresent)

    let orginalMethod = class_getInstanceMethod(self, orginalSelector)
    let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

    let didAddMethod = class_addMethod(self,
       orginalSelector,
       method_getImplementation(swizzledMethod!),
       method_getTypeEncoding(swizzledMethod!))

    if didAddMethod {
      class_replaceMethod(self, swizzledSelector,
        method_getImplementation(orginalMethod!),
        method_getTypeEncoding(orginalMethod!))
    } else {
      method_exchangeImplementations(orginalMethod!, swizzledMethod!)
    }
  }

  @objc
  private func swizzledPresent(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    if #available(iOS 13.0, *) {
      if viewControllerToPresent.modalPresentationStyle == .pageSheet
        || viewControllerToPresent.modalPresentationStyle == .automatic {
        viewControllerToPresent.modalPresentationStyle = .fullScreen
      }
    }

swizzledPresent(viewControllerToPresent, animated: flag, completion: completion) } }

then inside AppDelegate in application(_ application: didFinishLaunchingWithOptions) call:

UIViewController.swizzlePresent()

Photo by Arnel Hasanovic on Unsplash

We're building our future. Let's do this right - join us
READ ALSO FROM Programming
Read also
Need a successful project?
Estimate project or contact us