Interactive Delegate Methods Never Called

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

The NavigationController is called by a button, so there’s no interactive transition when presenting. It can be dismissed by a button or a UIPanGestureRecognizer, so it can be dismissed interactively or not.

  • How to use a function from the view controller in the app delegate?
  • Swift - how to open another viewcontroller with CollectionViewCell inside UITableViewCell
  • MKAnnotationView viewForAnnotation never called
  • Java equivalent of Cocoa delegates / Objective-C informal protocols?
  • NSURLConnection does not call complete across multiple Views
  • Passing back info between delegates on ObjectiveC
  • I have an object named TransitionManager for the transition, subclass of UIPercentDrivenInteractiveTransition.

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

    Moreover, when I press the buttons or swip (UIPanGestureRecognizer), the basic animation of the modal segue is done. So the two delegate methods animationControllerFor... don’t 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 triggered a modal segue to presenting the NavigationController

    • The NavigationController’s delegate outlet is linked to an object of the TransitionManager class

    • The NavigationController is referenced in the TransitionManager class by the property “navigationController”

    Solutions Collect From Internet About “Interactive Delegate Methods Never Called”

    I think the key problem is that you’re configuring the transitionDelegate in viewDidLoad. That’s often too late in the process. You should do it as you init the navigation controller.

    Let’s imagine your root scene (“Root”) that presents the navigation controller scene (“Nav”), that then pushes from scene A to B to C, for example, I’d imagine an object model like this, where the navigation controller would simply own 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” presents “nav”; and (b) a custom transition (interactive or not) when “nav” dismisses itself in order to return to the “root”. So, I’d subclass the navigation controller which:

    • adds a gesture recognizer to its view;

    • sets the transitioningDelegate to yield the custom animation as you transition from the root scene to the navigation controller scene (and back):

    • the transitioningDelegate will also return the interaction controller (which will only exist while the gesture recognizer is in progress), yielding interactive transition during gesture and non-interactive transition if you dismiss outside of the context of the gesture.

    In Swift 3, that looks like:

    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 be this custom subclass, and now the root scene can just present the navigation controller (with no special prepare(for:)) and everything just works.