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

Created on 14 Nov 2019  Â·  23Comments  Â·  Source: HeroTransitions/Hero

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

confirmed bug

Most helpful comment

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 :/

All 23 comments

Agree

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 :/

I have the same issue. And the statusbar will disappear when app enter background then back.

This is preventing all projects that rely on modals to use Hero, is there a solution?

I also ran into this issue. Is there a plan for fixing this? This is a major roadblock for my project.

Same here, did someone found a solution for this?

Any solution?

Still have this issue on iOS 14!

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.

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 |

Interesting. I discovered today that a nasty work around for this is presenting a dummy VC and dismissing it in .fullScreen mode. This is able to clean up the changes Hero made that breaks the native presentation. Unfortunately it can flicker a bit so it's not feasible as a solution. We must understand how to restore the hierarchy to the original setup.

yes, .fullScreen doesn't break anything. and also .currentContext doesn't break anything, but .overCurrentContext causes this issue.

  • .fullScreen and .currentContext : OK
  • .overFullScreen and .overCurrentContext : NO

This situation happens at least hero is used when dismissing. I mean, showing modal with normal way, and dismissing modal with Hero by setting hero.isEnabled = true before dismissing.

What I mean is, after having broken .pageSheet presentations when using hero transitions, one can fix the hierarchy by presenting and dismissing a dummy VC in fullscreen before trying to present your real VC in .pageSheet style.
I think it might be caused by this work around below, as it messes directly with the UIWindow hierarchy

if isPresenting != finished, !inContainerController, transitionContext != nil {
        // only happens when present a .overFullScreen VC
        // bug: http://openradar.appspot.com/radar?id=5320103646199808
        UIApplication.shared.keyWindow?.addSubview(isPresenting ? fromView : toView)
}

I think I found a solution for this issue.

HeroTransition.fromView and .toView is computed property which comes from HeroTransition.fromViewController?.view and .toViewController?.view : https://github.com/HeroTransitions/Hero/blob/develop/Sources/Transition/HeroTransition.swift#L176...L177

I changed those computed properties to stored properties by getting those values from context.view(forKey: .from) and context.view(forKey: .to) , like this:

// HeroTransition+UIViewControllerTransitioningDelegate.swift
public func animateTransition(using context: UIViewControllerContextTransitioning) {
    transitionContext = context
    fromViewController = fromViewController ?? context.viewController(forKey: .from)
    toViewController = toViewController ?? context.viewController(forKey: .to)
    fromView = fromView ?? context.view(forKey: .from)
    toView = toView ?? context.view(forKey: .to)
    transitionContainer = context.containerView
    start()
  }

and removed workaround mentioned in @nobre84 's post: https://github.com/HeroTransitions/Hero/blob/develop/Sources/Transition/HeroTransition+Complete.swift#L89...L93

ref: https://stackoverflow.com/questions/24338700/from-view-controller-disappears-using-uiviewcontrollercontexttransitioning/25901154#25901154

GitHub
Elegant transition library for iOS & tvOS. Contribute to HeroTransitions/Hero development by creating an account on GitHub.
GitHub
Elegant transition library for iOS & tvOS. Contribute to HeroTransitions/Hero development by creating an account on GitHub.
Stack Overflow
I got one problem and i have described it below. I am using UIViewControllerContextTransitioning for custom transitions. I have 2 view controllers, first view controller and second view controlle...

I did as well but got the issue from that thread which is black screen after transition as the presenting view is not restored to window of the transition messes with it which it seems Hero does. Did it work well in your test? I might have missed something 🤔

Em 20 de nov. de 2020, à(s) 04:37, Akira Fukunaga notifications@github.com escreveu:



I think I found a solution for this issue.

HeroTransition.fromView and .toView is computed property which comes from HeroTransition.fromViewController?.view and .toViewController?.view : https://github.com/HeroTransitions/Hero/blob/develop/Sources/Transition/HeroTransition.swift#L176...L177

I changed those computed properties to stored properties by getting those values from context.view(forKey: .from) and context.view(forKey: .to) , like this:

// HeroTransition+UIViewControllerTransitioningDelegate.swift
public func animateTransition(using context: UIViewControllerContextTransitioning) {
transitionContext = context
fromViewController = fromViewController ?? context.viewController(forKey: .from)
toViewController = toViewController ?? context.viewController(forKey: .to)
fromView = fromView ?? context.view(forKey: .from)
toView = toView ?? context.view(forKey: .to)
transitionContainer = context.containerView
start()
}

and removed workaround mentioned in @nobre84 's posthttps://github.com/HeroTransitions/Hero/issues/644#issuecomment-730815872: https://github.com/HeroTransitions/Hero/blob/develop/Sources/Transition/HeroTransition+Complete.swift#L89...L93

ref: https://stackoverflow.com/questions/24338700/from-view-controller-disappears-using-uiviewcontrollercontexttransitioning/25901154#25901154

[https://avatars2.githubusercontent.com/u/40681391?s=400&v=4]https://avatars2.githubusercontent.com/u/40681391?s=400&v=4
[https://camo.githubusercontent.com/b6a12909f1e31185a69a73d59208c507a992236d3230f9fc18e85058ae3d19e7/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f66617669636f6e732f66617669636f6e2e737667]https://camo.githubusercontent.com/b6a12909f1e31185a69a73d59208c507a992236d3230f9fc18e85058ae3d19e7/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f66617669636f6e732f66617669636f6e2e737667 GitHub
HeroTransitions/Herohttps://github.com/HeroTransitions/Hero
Elegant transition library for iOS & tvOS. Contribute to HeroTransitions/Hero development by creating an account on GitHub.
[https://avatars2.githubusercontent.com/u/40681391?s=400&v=4]https://avatars2.githubusercontent.com/u/40681391?s=400&v=4
[https://camo.githubusercontent.com/b6a12909f1e31185a69a73d59208c507a992236d3230f9fc18e85058ae3d19e7/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f66617669636f6e732f66617669636f6e2e737667]https://camo.githubusercontent.com/b6a12909f1e31185a69a73d59208c507a992236d3230f9fc18e85058ae3d19e7/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f66617669636f6e732f66617669636f6e2e737667 GitHub
HeroTransitions/Herohttps://github.com/HeroTransitions/Hero
Elegant transition library for iOS & tvOS. Contribute to HeroTransitions/Hero development by creating an account on GitHub.
[https://camo.githubusercontent.com/53e7f06238afdd6866aa6f07439d1736d67b758a664629734558f90266b1435e/68747470733a2f2f63646e2e737374617469632e6e65742f53697465732f737461636b6f766572666c6f772f496d672f6170706c652d746f7563682d69636f6e40322e706e673f763d373364373961383962646564]https://camo.githubusercontent.com/53e7f06238afdd6866aa6f07439d1736d67b758a664629734558f90266b1435e/68747470733a2f2f63646e2e737374617469632e6e65742f53697465732f737461636b6f766572666c6f772f496d672f6170706c652d746f7563682d69636f6e40322e706e673f763d373364373961383962646564
Stack Overflow
"From View Controller" disappears using UIViewControllerContextTransitioninghttps://stackoverflow.com/questions/24338700/from-view-controller-disappears-using-uiviewcontrollercontexttransitioning
I got one problem and i have described it below.

I am using UIViewControllerContextTransitioning for custom transitions.

I have 2 view controllers, first view controller and second view controlle...

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHubhttps://github.com/HeroTransitions/Hero/issues/644#issuecomment-730963574, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AADQGD54SB72ZXF2BLUXP63SQYME3ANCNFSM4JNI5NHQ.

Making myself clearer, when I start using fromView/toView from the transitionContext, there's a lot of code that actually trusts the fromView to be set and then Hero does nothing when doing a .overFullscreen transition. I'm playing with the code to try and make it more flexible to not having a fromView, but hasn't succeeded yet

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 }
    }

}

The final solution would be to refactor Hero to work without assuming fromView is free to use on non full screen transitions, which is not true since iOS 8.

yes, unfortunately, fromView from context can be nil...

UIApplication.shared.keyWindow?.addSubview(isPresenting ? fromView : toView)

I found why this workaround causes this bug. This is because adding fromView/toView to wrong position.

Ideal:

  • UIWindow

    • UITransitionView

    • UIDropShadowView



      • Here



but, actual:

  • UIWindow

    • UITransitionView

    • UIDropShadowView

    • Here

so, we have to add fromView/toView under correct UIDropShadowView, like this:

let transitionView = UIApplication.shared.keyWindow?.subviews.first
let dropShadowView = transitionView?.subviews.first
dropShadowView?.addSubview(isPresenting ? fromView : toView)

(Please be careful, the above code doesn't work when modal on modal.)

  1. Why fullScreen presentation style fixes this broken hierarchy?
    Because fullScreen presentation remove baseVC from DropShadowView, and re-add baseVC to correct DropShadowView when dismissing

  2. Why that workaround is needed when overFullScreen presentation style with Hero
    Because baseVC is removed from original DropShadowView when addSubview is called here.
    Maybe iOS doesn't recover baseVC when overFullScreen is specified because it's assumed baseVC is still added under DropShadowView.

Does this still happen in 1.6.0?
If so, can you make a PR please?

In my case this bug still exists in 1.6.1

I know it's an odd request from a manager, but I'm not super up to speed with a lot of heros workings.

If you have a patch could you please submit a pull request for review and I'll get to get this resolved as soon as possible. Thanks.

Was this page helpful?
0 / 5 - 0 ratings