Swift: UIPercentDrivenInteractiveTransition when canceled?

This is my first iOS development, so I use this tiny project to find out how the system works and how the language works (fast).

I am trying to create a drawer menu similar to an Android app and a certain number of iOS apps.

I found this tutorial that explains well how to do this and how it works: here

Now that I am using the NavigationController with the show, I need to change the way it is executed.

I changed the UIViewControllerTransitioningDelegate to UINavigationControllerDelegate so that I can override the navigationController function.

This means that I can pull out the box and fire it. It works well with a button or with a gesture. My problem is this: if I donโ€™t finish to drag the box far enough so that it reaches the threshold and finish the animation, it will be canceled and hidden. This is good and good, but when this happens, there is no call to the dismiss function, which means that the snapshot that I set in CurrentMenuAnimator is still in front of all layers, and I'm stuck there, although I can interact with what's behind it.

How can I fire or cancel using the NavigationController? Is it possible?

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

DismissMenuAnimator:

 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) } ) } } 
+5
source share
2 answers

To fix the problem, I added a check to PresentMenuAnimator to check if it was canceled. If that was then, delete the snapshot in UIView.Animate.

0
source

You can find out if the animation has been canceled, and you can catch it in the func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) method from UINavigationControllerDelegate .

Here is a code snippet on how to do this:

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

This method can be placed in your MenuNavigationController , where you can save your PresentMenuAnimator and say that the transition has been canceled, and there delete the picture that hangs around.

0
source

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


All Articles