// // SloppySwiper.m // // Created by Arkadiusz Holko http://holko.pl on 29-05-14. // #import "SloppySwiper.h" #import "SSWAnimator.h" #import "SSWDirectionalPanGestureRecognizer.h" @interface SloppySwiper() @property (weak, readwrite, nonatomic) UIPanGestureRecognizer *panRecognizer; @property (weak, nonatomic) IBOutlet UINavigationController *navigationController; @property (strong, nonatomic) SSWAnimator *animator; @property (strong, nonatomic) UIPercentDrivenInteractiveTransition *interactionController; /// A Boolean value that indicates whether the navigation controller is currently animating a push/pop operation. @property (nonatomic) BOOL duringAnimation; @end @implementation SloppySwiper #pragma mark - Lifecycle - (void)dealloc { [_panRecognizer removeTarget:self action:@selector(pan:)]; [_navigationController.view removeGestureRecognizer:_panRecognizer]; } - (instancetype)initWithNavigationController:(UINavigationController *)navigationController { NSCParameterAssert(!!navigationController); self = [super init]; if (self) { _navigationController = navigationController; [self commonInit]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self commonInit]; } - (void)commonInit { SSWDirectionalPanGestureRecognizer *panRecognizer = [[SSWDirectionalPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; panRecognizer.direction = SSWPanDirectionRight; panRecognizer.maximumNumberOfTouches = 1; [_navigationController.view addGestureRecognizer:panRecognizer]; _panRecognizer = panRecognizer; _animator = [[SSWAnimator alloc] init]; } #pragma mark - UIPanGestureRecognizer - (void)pan:(UIPanGestureRecognizer*)recognizer { UIView *view = self.navigationController.view; if (recognizer.state == UIGestureRecognizerStateBegan) { if (self.navigationController.viewControllers.count > 1 && !self.duringAnimation) { self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init]; self.interactionController.completionCurve = UIViewAnimationCurveEaseOut; [self.navigationController popViewControllerAnimated:YES]; } } else if (recognizer.state == UIGestureRecognizerStateChanged) { CGPoint translation = [recognizer translationInView:view]; // Cumulative translation.x can be less than zero because user can pan slightly to the right and then back to the left. CGFloat d = translation.x > 0 ? translation.x / CGRectGetWidth(view.bounds) : 0; [self.interactionController updateInteractiveTransition:d]; } else if (recognizer.state == UIGestureRecognizerStateEnded) { if ([recognizer velocityInView:view].x > 0) { [self.interactionController finishInteractiveTransition]; } else { [self.interactionController cancelInteractiveTransition]; // When the transition is cancelled, `navigationController:didShowViewController:animated:` isn't called, so we have to maintain `duringAnimation`'s state here too. self.duringAnimation = NO; } self.interactionController = nil; } } #pragma mark - UINavigationControllerDelegate - (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { if (operation == UINavigationControllerOperationPop) { return self.animator; } return nil; } - (id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id)animationController { return self.interactionController; } - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated { if (animated) { self.duringAnimation = YES; } } - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated { self.duringAnimation = NO; if (navigationController.viewControllers.count <= 1) { self.panRecognizer.enabled = NO; } else { self.panRecognizer.enabled = YES; } } @end