0

https://i.stack.imgur.com/kqKLf.gif

Problem:

When implementing the Present transition using UIViewControllerAnimatedTransitioning and UIViewControllerInteractiveTransitioning, if modalPresentationStyle is not .fullScreen, the return of the view(forKey: .from) method of UIViewControllerContextTransitioning is nil. In the case of Dismiss, on the contrary, the return of view(forKey: .to) is nil. So, if I use the viewController view returned by viewController(forKey: .to) for animation, when the transition is complete, nothing remains in the view layer, and a black screen is displayed.

SomePresentingViewController.swift

let somePresentedViewController = SomePresentedViewController()
somePresentedViewController.transitioningDelegate = somePresentedViewController.transitionController
            
self.present(somePresentedViewController, animated: true, completion: nil)

SomePresentedViewController.swift

class SomePresentedViewController: UIViewController {
    var transitionController = TransitionController()

    @IBAction func closeButtonTapped(_ sender: Any) {
        self.dismiss(animated: true, completion: nil)
    }
    ...

TransitionController.swift

class TransitionController: NSObject, UIViewControllerTransitioningDelegate {
    let animator: SlideAnimator

    override init() {
        animator = SlideAnimator()
        
        super.init()
    }

    func animationController(
        forPresented presented: UIViewController,
        presenting: UIViewController,
        source: UIViewController
    ) -> UIViewControllerAnimatedTransitioning? {
        animator.isPresenting = true
        return animator
    }
    
    func animationController(
        forDismissed dismissed: UIViewController
    ) -> UIViewControllerAnimatedTransitioning? {
        animator.isPresenting = false
        return animator
    }
}

SlideAnimator.swift

class SlideAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    var isPresenting: Bool = true

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

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        print(transitionContext.containerView)
        
        if isPresenting {
            animateSlideUpTransition(using: transitionContext)
        } else {
            animateSlideDownTransition(using: transitionContext)
        }
    }

    func animateSlideDownTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: .from),
            let toVC = transitionContext.viewController(forKey: .to) else {
                transitionContext.completeTransition(false)
                return
        }
        
        let container = transitionContext.containerView
        let screenOffDown = CGAffineTransform(translationX: 0, y: container.frame.height)

        containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext),
                       delay: 0.0,
                       options: .curveEaseInOut,
                       animations: {
                        
                        fromVC.view.transform = screenOffDown
        }) { (success) in
            fromVC.view.transform = .identity
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }

    ...
}

Information gathering:

iOS13 bug? In the link below, you can see that the radar for the issue is open to this day.

http://www.openradar.me/radar?id=4999313432248320

There have been many comments that this is a bug in iOS 13 on stackoverflow. But there have been some more compelling comments that this is Apple's intention and why:

https://stackoverflow.com/a/25901154/5705503

A possible cause:

If NO is returned from -shouldRemovePresentersView, the view associated with UITransitionContextFromViewKey is nil during presentation. This intended to be a hint that your animator should NOT be manipulating the presenting view controller's view. For a dismissal, the -presentedView is returned.

Why not allow the animator manipulate the presenting view controller's view at all times? First of all, if the presenting view controller's view is going to stay visible after the animation finishes during the whole presentation life cycle there is no need to animate it at all — it just stays where it is. Second, if the ownership for that view controller is transferred to the presentation controller, the presentation controller will most likely not know how to layout that view controller's view when needed, for example when the orientation changes, but the original owner of the presenting view controller does.

-Apple Documentation Archive- https://developer.apple.com/library/archive/samplecode/CustomTransitions/Listings/CustomTransitions_Custom_Presentation_AAPLCustomPresentationController_m.html

Attempts to solve:

  1. Set modalPresentationStyle to .fullScreen.
  • I don't pick this solve because the background of the present view controller must be translucent and the view controller behind it must be visible.
  1. Create a new project to see if the problem recurs
  • Problem reproduced!
  1. Build on iOS12
  • Problem reproduced
  1. Subclass UIPresentationController to override shouldRemovePresentersView property to return false and adopt it as presentationController.
  • I tried the solution mentioned in the link below, but the results were not different. https://stackoverflow.com/a/41314396/5705503 The role of shouldRemovePresentersView as I know it: Indicates whether the view of the view controller being switched is removed from the window at the end of the presentation transition.
  1. Substitute the view of the view controller returned by viewController(forKey:) to use it for animation and add the view to the key window.
  • I was able to achieve the desired result, but it was a bad approach and I did not adopt it as a workaround as it could cause various problems in the future.

Environments:

  • iOS 13.5 simulator, iOS 13.5.1 iPhoneXS
  • Xcode 11.5 (11E608c)

Addition)

I found an old Sample where the same problem occurs (black screen is visible when dismiss transition is complete) in the same situation as me.

Please, I hope someone can clone this Sample and run it and let me know how to fix this.

https://www.thorntech.com/2016/03/ios-tutorial-make-interactive-slide-menu-swift/

Munok Kim
  • 70
  • 1
  • 5
  • https://stackoverflow.com/help/minimal-reproducible-example – Magnas Jul 31 '20 at 06:12
  • I'm Sorry. I forgot the codes. I added the codes now. – Munok Kim Jul 31 '20 at 07:53
  • Are you claiming that all non fullscreen presented view controller custom animations fail? Because if you are, that’s clearly not true. – matt Aug 03 '20 at 03:43
  • @matt oh, I have no intention of claiming something, but I am not sure what I am doing wrong. I've written down the attempts what I have tried so far. and I'm hoping someone can help me. – Munok Kim Aug 03 '20 at 13:55
  • Would it be useful to you to see examples of presentation / dismissal custom transitions that work? – matt Aug 03 '20 at 14:37
  • @matt Yes, It helps to me. because I always failed presentation / dismissal custom transitions using `UIViewControllerAnimatedTransitioning` and `UIViewControllerInteractiveTransitioning`, without using `ModalPresentationStyle` `.fullScreen`. – Munok Kim Aug 04 '20 at 03:16
  • Ok, https://github.com/mattneub/custom-alert-view-iOS7 – matt Aug 04 '20 at 03:32
  • @matt Thanks a lot! I'll try and report how this works. – Munok Kim Aug 04 '20 at 06:23
  • @matt I tried in your Sample, but I can't find out something special. I tried substract for `presentationController` in your `transitioner` like my case. It still ok, and no black screen. Thanks for your help. Unfortunately, I decide that choose the solution no 5 in my Question. It works well. – Munok Kim Aug 04 '20 at 07:04

0 Answers0