16

I've implemented a custom transition between two view controllers in my iOS app and it worked fine with iOS 10, 11, and 12.

Now I want make it ready for iOS 13 using Xcode 11 beta 6 and iOS 13 beta 8, but the transition is stuck.

The custom transition should move the first view controller up and out of the screen and the second one from bottom up. But now it falls back to iOS13 default presentation style pageSheet, just scales the first view controller down a little bit and adds a dimmed overlay. But the second view doesn't appear.

I've found that in the method animatePresentation(context: UIViewControllerContextTransitioning) the context doesn't return a 'from' view, so context.view(forKey: .from) returns nil.

What am I supposed to do without a 'from' view?

FlyUpTransition.swift

class FlyUpTransition: NSObject, UIViewControllerAnimatedTransitioning {

    var mode: Mode = .present

    enum Mode {
        case present
        case dismiss
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return TimeInterval(0.45)
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        switch mode {
        case .present:
            animatePresentation(context: transitionContext)
        case .dismiss:
            animateDismissal(context: transitionContext)
        }
    }

    func animatePresentation(context: UIViewControllerContextTransitioning) {
        guard let fromView = context.view(forKey: .from), let toView = context.view(forKey: .to) else { return }
        ...
    }

    func animateDismissal(context: UIViewControllerContextTransitioning) {
        guard let fromView = context.view(forKey: .from), let toView = context.view(forKey: .to) else { return }
        ...
    }
}
Lukas Würzburger
  • 6,543
  • 7
  • 41
  • 75
  • I am also encountering this issue. It seems like the `from` view is no longer being provided whenever the view you are transitioning from is not being presented modally. It seems to occur on popovers as well. No relief in the iOS 13.1 beta either. – Chris Vasselli Sep 18 '19 at 15:01
  • 1
    This is not a bug and it isn’t new. There was never a from view in a non fullscreen presentation. – matt Sep 27 '19 at 02:56
  • 1
    @matt care to elaborate a bit? Why would it be intentional that there not be a view provided in non-fullscreen presentation? FWIW, I can confirm that I'm getting a from view in iOS 12 from a popover on iPad. – Chris Vasselli Oct 18 '19 at 21:03
  • 3
    Hi @ChrisVasselli When you do a custom transition animation, if you are not doing a fullscreen presentation (like let's say you are doing overFullScreen or overCurrentContext), the presenting view controller does not move; its view just remains in place. It cannot be animated because it is not going to go anywhere. It is just going to sit there behind the presented view controller's view. So it is not involved the animation and it is not moved into the transition context's world. – matt Oct 18 '19 at 21:08
  • @matt Hmm, I think we might be talking about different scenarios. Where I'm seeing the "from" view missing is when I'm trying to use a custom transition _from_ a view that's been presented non-modally, not _to_ a view that's being presented non-modally. I created a test project for my bug report if you want to see what I'm talking about: https://github.com/clindsay/TestTransitionBug – Chris Vasselli Oct 20 '19 at 18:13
  • 1
    No, we're talking about the same thing. Your button Attempt Custom Transition leads to a view controller whose Presentation is set to Automatic. In iOS 13, that means a Page Sheet on iPad. That's not full screen. So there is no From view because the From view cannot be involved in any kind of animation; it can never move. There's no bug here. – matt Oct 20 '19 at 18:52
  • @matt I tried changing the presentation style on the presented view controller and the segue to full screen, and there is still no from view. – Chris Vasselli Nov 06 '19 at 14:59
  • 1
    @ChrisVasselli I don't know what you have now. I downloaded your github example and made _all three_ view controllers and _both_ segues Full Screen and ran it, and it prints "From view DOES exist." And that is what I expect. I do _not_ expect the From view to exist if either of the views in a custom presentation transition is NOT full screen, because that would make no sense. That is why I say there is no bug here. Maybe the bug was in iOS 12 and before? – matt Nov 06 '19 at 16:13
  • @matt I just updated the project in git to show you what I'm looking at, which is when the from view is not fullscreen. Run that example in iOS 12 and you'll see "from view DOES exist", and in iOS 13 "from view does NOT exist". Sounds like maybe you think the iOS 12 behavior was a bug? I'm not sure. But it's definitely changed from iOS 12 to iOS 13. In my case, I do animate one of the subviews of the from view as part of the transition, so it makes sense to me that the from view would be there. But it sounds like you think maybe I'm just supposed to access that subview via other means? – Chris Vasselli Nov 07 '19 at 17:58
  • 1
    I agree. But I am suggesting that the changed behavior in iOS 13 is perhaps not a bug. Rather, I am suggesting this was an edge case behaving incorrectly in iOS 12. Of course I could be wrong about that! – matt Nov 07 '19 at 18:05

4 Answers4

17

TL;DR

This is a bug in iOS, but you can use context.viewController(forKey:.from).view as a workaround.

Full Details

This appears to be a bug in iOS 13.0. As of iOS 13.1 beta 3, it's still there as well. http://www.openradar.me/radar?id=4999313432248320

The transition context's view(forKey:) method is incorrectly returning nil under certain circumstances. It appears this is happening for view(forKey: .from) when the presenting view controller is being presented non-modally. When dismissing a view controller that was originally presented from a non-modal view controller, the result of view(forKey: .to) is also nil.

I've observed this not only on the new sheet-style presentation on iPhone, but also in normal form sheets and popovers on iPad.

Another manifestation of this issue seems to be that the finalFrame(for:) method returns an incorrect CGRect when asked what the final frame for this view controller should be. In my testing, it is returning a full-screen rect, even though the correct view is smaller.

A workaround is to use the root view controller of view controller returned by viewController(forKey:) method, although the documentation explicitly discourages that: "The view returned by this method may or may not be the root view of the corresponding view controller."

Chris Vasselli
  • 13,064
  • 4
  • 46
  • 49
  • Xcode 11.0, iOS 13.1 (17A5837a), the bug still exists. I will try the latest iOS later and update the comment – infinity_coding7 Nov 04 '19 at 22:38
  • iOS 13.1 and iOS 13.1.2, Xcode 11.1, I tried the same animator with both transitionContext.view(forKey: .from) and transitionContext.view(forKey: .from), toView and fromView are properly returned and transition could performed as expedited. The status of the radar is open but it seems this problem is fixed. – infinity_coding7 Nov 13 '19 at 15:49
  • This bug destroyed my custom transition, thanks Apple. – Pedro Paulo Amorim Dec 17 '19 at 12:06
12

Ok, it was easy, even though, it's a breaking API change of Apple.

viewController.modalPresentationStyle = .fullScreen

Now I have to go through my whole project and check all modal presentations if they still look as I need them to.

James
  • 3,597
  • 2
  • 39
  • 38
Lukas Würzburger
  • 6,543
  • 7
  • 41
  • 75
2

The answer above is correct to set modalPresentationStyle to .fullScreen, however it is also worth to mention that if your view controller is embedded in a UINavigationController, you need to set it on the navigation controller:

navigationController.modalPresentationStyle = .fullScreen
Lukas Würzburger
  • 6,543
  • 7
  • 41
  • 75
Bastek
  • 899
  • 1
  • 12
  • 27
0

I set up the segue in IB by drag-and-drop from a collection VC to another VC, which is used to display details.

I have a new discoveries on this problem, To refer to 'toView' and 'fromView', both of the following methods work

Indirectly way:

transitionContext.viewController(forKey: .to)?.view
transitionContext.viewController(forKey: .from)?.view

Directly way:

transitionContext.view(forKey: .to)
transitionContext.view(forKey: .from)

But when I switched the segue style to 'Over Full Screen', the directly way return 'nil' for both 'toView' and 'fromView' and only indirectly way work.

Hope this will be helpful to someone in the future.

P.S. This is my discovery along my way to solve another problem, which might be helpful, if you also have encounter the problem that 'a working animator' stops working in iOS 13 and above

infinity_coding7
  • 434
  • 5
  • 16