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:
- 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.
- Create a new project to see if the problem recurs
- Problem reproduced!
- Build on iOS12
- Problem reproduced
- Subclass
UIPresentationController
to overrideshouldRemovePresentersView
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.
- 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/