Hero: [Bug] Modal transitions stopped working after first hero transition

What did you do?

I use the following to display a VC with a modal transition: self.present(vc, animated: true) {}

What did you expect to happen?

I expect the normal behavior of IOS 13: IMG_6509

However, this only works before using a Hero transition at another place in the app the first time!

What happened instead?

As soon as I have used Hero Transition the first time elsewhere in the app, the same modal transition behaves like this:

IMG_6510

From this point, all modal transitions in the app behave like this.

Any ideas? Thanks in advance!

  • Hero Version: 1.5.0

  • iOS Version(s): 13.2

  • Swift Version: 5.0

  • Devices/Simulators: Both

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 14
  • Comments: 24

Most upvoted comments

I’m experiencing the same issue, is there a known workaround for it?
I would prefer not to have to remove Hero from my project but it is currently blocking me from updating to SDK 13 properly 😕

Any solution?

Okay I think I’ve got a solid work around:

import UIKit

extension UIViewController {

    /// Hero does not honor UIViewControllerTransitioning's contract correctly on transitions that show through the previous view controller, leaving the hierarchy wrong on dismissal. In order to present with .automatic, .formSheet or .pageSheet styles, we do a work around that allows UIKit to fix the window hierarchy before presenting.
    /// - Parameters:
    ///   - viewController: the view controller to present
    ///   - animated: wether to animate the presentation
    ///   - completionHandler: handler called when the new view controller has appeared
    func presentWithHeroFix(_ viewController: UIViewController,
                            animated: Bool,
                            completion: (() -> Void)? = nil) {

        func presentAction() {
            present(viewController, animated: animated, completion: completion)
        }

        guard #available(iOS 13, *),
              viewController.modalPresentationStyle != .fullScreen,
              let windowSnapshot = view.window?.snapshotView(afterScreenUpdates: false)
        else {
            print("Could not get window snapshot to presentWithHeroFix")
            return presentAction()
        }

        // Now we present and dismiss a view controller to allow UIKit to rebuild the window's presenting hierarchy. The view is just a snapshot of the current window contents to avoid any flickering.
        let snapshotViewController = SnapshottingViewController(snapshot: windowSnapshot)
        present(snapshotViewController, animated: false) {
            snapshotViewController.dismiss(animated: false) {
                presentAction()
            }
        }

    }

}

private class SnapshottingViewController: UIViewController {

    private let snapshot: UIView

    init(snapshot: UIView) {
        self.snapshot = snapshot

        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        view = snapshot
    }

    override open var modalPresentationStyle: UIModalPresentationStyle {
        get { return .fullScreen }
        set { _ = newValue }
    }

}

I found differences of view hierarchy before/after using Hero modal. the position of UIDimmingView is different.

View hierarchy of normal modal (not Hero) before using hero modal

  • UIWindow -> UITransitionView -> UIDropShadowView -> BaseVC -> UIDimmingView -> UITransitionView -> … -> PresentedVC(Modal)

View hierarchy of normal modal (not Hero) after using hero modal

  • UIWindow -> UITransitionView -> UIDropShadowView -> UIDimmingView -> BaseVC -> UITransitionView -> … -> PresentedVC(Modal)

Here is screenshot list. the highlighted view is UIDimmingView

Before After
Screen Shot 2020-11-20 at 10 40 46 Screen Shot 2020-11-20 at 10 41 11

Any hint as to why this happen so I could analyze the source code? I’m perfectly OK in not using hero on the flows I need native pageSheet/formSheet presentations, but if you use Hero anywhere else it will break all future native modals.