Интерактивные методы делегата никогда не вызывались

Я хочу сделать интерактивный переход между ViewController (1) и NavigationViewController (2).

NavigationController вызывается кнопкой, поэтому при представлении интерактивный переход отсутствует. Он может быть отклонен кнопкой или UIPanGestureRecognizer, поэтому он может быть отклонен в интерактивном режиме или нет.

У меня есть объект с именем TransitionManager для перехода, подкласс UIPercentDrivenInteractiveTransition.

Проблема с кодом ниже состоит в том, что два метода делегата interactionControllerFor... никогда не называются.

Более того, когда я нажимаю кнопки или проводку (UIPanGestureRecognizer), основная анимация модального перехода завершается. Таким образом, два метода делегата animationControllerFor... тоже не работает

Есть идеи? Спасибо

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

  • Кнопка на ViewController вызвала модальный переход к представлению NavigationController

  • Выход делегата NavigationController связан с объектом класса TransitionManager

  • На NavigationController ссылаются в классе TransitionManager свойство "navigationController"

1 ответ

Решение

Я думаю, что ключевая проблема в том, что вы настраиваете transitionDelegate в viewDidLoad, Это часто слишком поздно в процессе. Ты должен делать это как ты init навигационный контроллер.

Давайте представим вашу корневую сцену ("Root"), которая представляет сцену контроллера навигации ("Nav"), которая затем перемещается от сцены A к B, например, к C, я представляю объектную модель, подобную этой, где контроллер навигации будет просто иметь собственный контроллер анимации, контроллер взаимодействия и распознаватель жестов:

просмотр иерархии контроллеров и объектной модели

Это все, что вам нужно при рассмотрении (а) пользовательского перехода (неинтерактивного), когда "root" представляет "nav"; и (b) пользовательский переход (интерактивный или нет), когда "nav" отклоняется, чтобы вернуться к "root". Итак, я бы подкласс навигационного контроллера, который:

  • добавляет распознаватель жестов к своему виду;

  • устанавливает transitioningDelegate чтобы получить пользовательскую анимацию при переходе от корневой сцены к сцене контроллера навигации (и обратно):

  • transitioningDelegate также вернет контроллер взаимодействия (который будет существовать только во время выполнения распознавателя жестов), предоставляя интерактивный переход во время жеста и неинтерактивный переход, если вы отклоняете его вне контекста жеста.

В Swift 3 это выглядит так:

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)
        })
    }
}

Таким образом, я могу просто изменить базовый класс моего контроллера навигации в раскадровке на этот пользовательский подкласс, и теперь корневая сцена может просто представить контроллер навигации (без специальных prepare(for:)) и все просто работает.

Другие вопросы по тегам