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