Interactive delegate methods that have never been called

I want to make an interactive transition between ViewController (1) and NavigationViewController (2).

The navigation controller is called by the button, so there is no interactive transition when presenting. It can be rejected with the button or UIPanGestureRecognizer, so it can be rejected interactively or not.

I have a TransitionManager object to navigate to, a subclass of UIPercentDrivenInteractiveTransition.

The problem with the code below is that the two delegate methods of interactionControllerFor... never called.

In addition, when I press buttons or switch (UIPanGestureRecognizer), the main animation of the modal segment is executed. Thus, both animationControllerFor... delegate methods do not work either.

Any ideas? Thanks

ViewController.swift

 let transitionManager = TransitionManager() override func viewDidLoad() { super.viewDidLoad() self.transitioningDelegate = transitionManager } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { let dest = segue.destinationViewController as UIViewController dest.transitioningDelegate = transitionManager dest.modalPresentationStyle = .Custom } 

TransitionManager.swift

 class TransitionPushManager: UIPercentDrivenInteractiveTransition, UINavigationControllerDelegate, UIViewControllerTransitioningDelegate { @IBOutlet var navigationController: UINavigationController! var animation : Animator! // Implement UIViewControllerAnimatedTransitioning protocol override func awakeFromNib() { var panGesture = UIPanGestureRecognizer(target: self, action: "gestureHandler:") navigationController.view.addGestureRecognizer(panGesture) animation = Animator() } func gestureHandler(pan : UIPanGestureRecognizer) { switch pan.state { case .Began : interactive = true navigationController.presentingViewController?.dismissViewControllerAnimated(true, completion:nil) case .Changed : ... default : ... interactive = false } } func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return animation } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return animation } func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return nil } func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return self.interactive ? self : nil } 

Main.storyboard

  • The button on the ViewController caused a modal transition to the NavigationController view.

  • The NavigationController delegate output is associated with a TransitionManager class object

  • The NavigationController pointer in the TransitionManager class refers to the "navigationController" property

+6
source share
1 answer

I think the main problem is that you are setting transitionDelegate to viewDidLoad . It is often too late in the process. You must do this as an init navigation controller.

Imagine your root scene (β€œRoot”), which represents a navigation controller (β€œNav”) scene, which then pushes from scene A to B to C, for example, I would assume that such an object model, the navigation controller would just have its own animation controller, interaction controller and gesture recognizer:

view controller hierarchy and object model

This is all you need when considering (a) a custom transition (non-interactive) when "root" represents "nav"; and (b) a user transition (interactive or not) when the "nav" deviates from itself to return to the "root". So, I would subclass the navigation controller, which:

  • Adds a gesture recognizer to your presentation

  • sets transitioningDelegate to receive custom animation when switching from the root scene to the scene of the navigation controller (and vice versa):

  • transitioningDelegate will also return an interaction controller (which will only exist while the gesture recognizer is in the process), which provides an interactive transition during gestures and a non-interactive transition if you deviate out of context of gestures.

In Swift 3, it looks like this:

 import UIKit import UIKit.UIGestureRecognizerSubclass class CustomNavigationController: UINavigationController { public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) configure() } override init(rootViewController: UIViewController) { super.init(rootViewController: rootViewController) configure() } private func configure() { transitioningDelegate = self // for presenting the original navigation controller } override func viewDidLoad() { super.viewDidLoad() delegate = self // for navigation controller custom transitions let left = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeFromLeft(_:))) left.edges = .left view.addGestureRecognizer(left) } fileprivate var interactionController: UIPercentDrivenInteractiveTransition? func handleSwipeFromLeft(_ gesture: UIScreenEdgePanGestureRecognizer) { let percent = gesture.translation(in: gesture.view!).x / gesture.view!.bounds.size.width if gesture.state == .began { interactionController = UIPercentDrivenInteractiveTransition() if viewControllers.count > 1 { popViewController(animated: true) } else { dismiss(animated: true) } } else if gesture.state == .changed { interactionController?.update(percent) } else if gesture.state == .ended { if percent > 0.5 && gesture.state != .cancelled { interactionController?.finish() } else { interactionController?.cancel() } interactionController = nil } } } // MARK: - UINavigationControllerDelegate // // Use this for custom transitions as you push/pop between the various child view controllers // of the navigation controller. If you don't need a custom animation there, you can comment this // out. extension CustomNavigationController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == .push { return ForwardAnimator() } else if operation == .pop { return BackAnimator() } return nil } func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactionController } } // MARK: - UIViewControllerTransitioningDelegate // // This is needed for the animation when we initially present the navigation controller. // If you're only looking for custom animations as you push/pop between the child view // controllers of the navigation controller, this is not needed. This is only for the // custom transition of the initial `present` and `dismiss` of the navigation controller // itself. extension CustomNavigationController: UIViewControllerTransitioningDelegate { func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return ForwardAnimator() } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return BackAnimator() } func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactionController } func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactionController } func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return PresentationController(presentedViewController: presented, presenting: presenting) } } // When doing custom `present`/`dismiss` that overlays the entire // screen, you generally want to remove the presenting view controller's // view from the view hierarchy. This presentation controller // subclass accomplishes that for us. class PresentationController: UIPresentationController { override var shouldRemovePresentersView: Bool { return true } } // You can do whatever you want in the animation; I'm just fading class ForwardAnimator : NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.5 } func animateTransition(using context: UIViewControllerContextTransitioning) { let toView = context.viewController(forKey: .to)!.view! context.containerView.addSubview(toView) toView.alpha = 0.0 UIView.animate(withDuration: transitionDuration(using: context), animations: { toView.alpha = 1.0 }, completion: { finished in context.completeTransition(!context.transitionWasCancelled) }) } } class BackAnimator : NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.5 } func animateTransition(using context: UIViewControllerContextTransitioning) { let toView = context.viewController(forKey: .to)!.view! let fromView = context.viewController(forKey: .from)!.view! context.containerView.insertSubview(toView, belowSubview: fromView) UIView.animate(withDuration: transitionDuration(using: context), animations: { fromView.alpha = 0.0 }, completion: { finished in context.completeTransition(!context.transitionWasCancelled) }) } } 

So, I can just change the base class of my navigation controller in the storyboard to become this custom subclass, and now the root scene can just represent the navigation controller (without special prepare(for:) ), and everything just works.

+16
source

Source: https://habr.com/ru/post/977556/


All Articles