Интерактивные методы делегата никогда не вызывались
Я хочу сделать интерактивный переход между 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:)
) и все просто работает.