Swift: UIPercentDrivenInteractiveTransition en cancelar?

Este es mi primer desarrollo de iOS y estoy usando este pequeño proyecto para aprender cómo funciona el sistema y cómo funciona el lenguaje (swift).

Estoy tratando de hacer un menu de cajones similar a la aplicación de Android y un cierto número de aplicaciones de iOS.

Encontré este tutorial que explica bien cómo hacerlo y cómo funciona: aquí

Ahora, ya que estoy usando un NavigationController con show, tengo que modificar la forma en que se hace.

Cambié el UIViewControllerTransitioningDelegate a UINavigationControllerDelegate para que pueda anular la function navigationController .

Esto significa que puedo sacar el cajón y descartarlo. Funciona bien con un button o con el gesto. Mi problema es el siguiente: si no termino de arrastrar el cajón lo suficiente para que scope el umbral y termine la animation, se cancelará y se ocultará. Todo esto está bien pero cuando eso sucede no hay llamada a una function de rechazo, lo que significa que la instantánea que puse en su lugar en PresentMenuAnimator todavía está delante de todas las capas y estoy atrapado allí a pesar de que puedo interactuar con lo que está detrás eso.

¿Cómo puedo atrapar una baja o una cancelación con el control de navigation? Es eso posible ?

Interactor:

import UIKit class Interactor:UIPercentDrivenInteractiveTransition { var hasStarted: Bool = false; var shouldFinish: Bool = false; } 

MenuHelper:

 import Foundation import UIKit enum Direction { case Up case Down case Left case Right } struct MenuHelper { static let menuWith:CGFloat = 0.8; static let percentThreshold:CGFloat = 0.6; static let snapshotNumber = 12345; static func calculateProgress(translationInView:CGPoint, viewBounds:CGRect, direction: Direction) -> CGFloat { let pointOnAxis:CGFloat; let axisLength:CGFloat; switch direction { case .Up, .Down : pointOnAxis = translationInView.y; axisLength = viewBounds.height; case .Left, .Right : pointOnAxis = translationInView.x; axisLength = viewBounds.width; } let movementOnAxis = pointOnAxis/axisLength; let positiveMovementOnAxis:Float; let positiveMovementOnAxisPercent:Float; switch direction { case .Right, .Down: positiveMovementOnAxis = fmaxf(Float(movementOnAxis), 0.0); positiveMovementOnAxisPercent = fminf(positiveMovementOnAxis, 1.0); return CGFloat(positiveMovementOnAxisPercent); case .Left, .Up : positiveMovementOnAxis = fminf(Float(movementOnAxis), 0.0); positiveMovementOnAxisPercent = fmaxf(positiveMovementOnAxis, -1.0); return CGFloat(-positiveMovementOnAxisPercent); } } static func mapGestureStateToInteractor(gestureState:UIGestureRecognizerState, progress:CGFloat, interactor: Interactor?, triggerSegue: () -> Void ) { guard let interactor = interactor else {return }; switch gestureState { case .began : interactor.hasStarted = true; interactor.shouldFinish = false; triggerSegue(); case .changed : interactor.shouldFinish = progress > percentThreshold; interactor.update(progress); case .cancelled : interactor.hasStarted = false; interactor.shouldFinish = false; interactor.cancel(); case .ended : interactor.hasStarted = false; interactor.shouldFinish ? interactor.finish() : interactor.cancel(); interactor.shouldFinish = false; default : break; } } } 

MenuNavigationController:

 import Foundation import UIKit class MenuNavigationController: UINavigationController, UINavigationControllerDelegate { let interactor = Interactor() override func viewDidLoad() { super.viewDidLoad(); self.delegate = self; } func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if((toVC as? MenuViewController) != nil) { return PresentMenuAnimator(); } else { return DismissMenuAnimator(); } } func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactor.hasStarted ? interactor : nil; } } 

PresentMenuAnimator:

 import UIKit class PresentMenuAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.6; } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else {return}; let containerView = transitionContext.containerView; containerView.insertSubview(toVC.view, aboveSubview: fromVC.view); let snapshot = fromVC.view.snapshotView(afterScreenUpdates: false); snapshot?.tag = MenuHelper.snapshotNumber; snapshot?.isUserInteractionEnabled = false; snapshot?.layer.shadowOpacity = 0.7; containerView.insertSubview(snapshot!, aboveSubview: toVC.view); fromVC.view.isHidden = true; UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {snapshot?.center.x+=UIScreen.main.bounds.width*MenuHelper.menuWith;}, completion: {_ in fromVC.view.isHidden = false; transitionContext.completeTransition(!transitionContext.transitionWasCancelled);} ); } } 

DesmissMenuAnimator:

 import UIKit class DismissMenuAnimator : NSObject { } extension DismissMenuAnimator : UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.6; } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return } let containerView = transitionContext.containerView; let snapshot = containerView.viewWithTag(MenuHelper.snapshotNumber) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { snapshot?.frame = CGRect(origin: CGPoint.zero, size: UIScreen.main.bounds.size) }, completion: { _ in let didTransitionComplete = !transitionContext.transitionWasCancelled if didTransitionComplete { containerView.insertSubview(toVC.view, aboveSubview: fromVC.view) snapshot?.removeFromSuperview() } transitionContext.completeTransition(didTransitionComplete) } ) } } 

    Es posible saber si la animation se canceló y se puede capturar en el método func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) método de UINavigationControllerDelegate .

    Aquí hay un fragment de código sobre cómo hacerlo:

     func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { navigationController.transitionCoordinator?.notifyWhenInteractionEnds { context in if context.isCancelled { // The interactive back transition was cancelled } } } 

    Este método podría colocarse en su MenuNavigationController , en el cual podría persistir su PresentMenuAnimator y decirle que la transición fue cancelada, y allí eliminar la instantánea que está dando vueltas.

    Para solucionar el problema, agregué una verificación en PresentMenuAnimator para verificar si la animation se había cancelado. Si fue así, elimine la instantánea en UIView.Animate.