123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- // The MIT License (MIT)
- //
- // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog )
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in all
- // copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- // SOFTWARE.
- #import "UINavigationController+FDFullscreenPopGesture.h"
- #import <objc/runtime.h>
- @interface _FDFullscreenPopGestureRecognizerDelegate : NSObject <UIGestureRecognizerDelegate>
- @property (nonatomic, weak) UINavigationController *navigationController;
- @end
- @implementation _FDFullscreenPopGestureRecognizerDelegate
- - (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
- {
- // Ignore when no view controller is pushed into the navigation stack.
- if (self.navigationController.viewControllers.count <= 1) {
- return NO;
- }
-
- // Ignore when the active view controller doesn't allow interactive pop.
- UIViewController *topViewController = self.navigationController.viewControllers.lastObject;
- if (topViewController.fd_interactivePopDisabled) {
- return NO;
- }
-
- // Ignore when the beginning location is beyond max allowed initial distance to left edge.
- CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
- CGFloat maxAllowedInitialDistance = topViewController.fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
- if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
- return NO;
- }
- // Ignore pan gesture when the navigation controller is currently in transition.
- if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
- return NO;
- }
-
- // Prevent calling the handler when the gesture begins in an opposite direction.
- CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
- BOOL isLeftToRight = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight;
- CGFloat multiplier = isLeftToRight ? 1 : - 1;
- if ((translation.x * multiplier) <= 0) {
- return NO;
- }
-
- return YES;
- }
- @end
- typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);
- @interface UIViewController (FDFullscreenPopGesturePrivate)
- @property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;
- @end
- @implementation UIViewController (FDFullscreenPopGesturePrivate)
- + (void)load
- {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- Method viewWillAppear_originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
- Method viewWillAppear_swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillAppear:));
- method_exchangeImplementations(viewWillAppear_originalMethod, viewWillAppear_swizzledMethod);
-
- Method viewWillDisappear_originalMethod = class_getInstanceMethod(self, @selector(viewWillDisappear:));
- Method viewWillDisappear_swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillDisappear:));
- method_exchangeImplementations(viewWillDisappear_originalMethod, viewWillDisappear_swizzledMethod);
- });
- }
- - (void)fd_viewWillAppear:(BOOL)animated
- {
- // Forward to primary implementation.
- [self fd_viewWillAppear:animated];
-
- if (self.fd_willAppearInjectBlock) {
- self.fd_willAppearInjectBlock(self, animated);
- }
- }
- - (void)fd_viewWillDisappear:(BOOL)animated
- {
- // Forward to primary implementation.
- [self fd_viewWillDisappear:animated];
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- UIViewController *viewController = self.navigationController.viewControllers.lastObject;
- if (viewController && !viewController.fd_prefersNavigationBarHidden) {
- [self.navigationController setNavigationBarHidden:NO animated:NO];
- }
- });
- }
- - (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
- {
- return objc_getAssociatedObject(self, _cmd);
- }
- - (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
- {
- objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
- }
- @end
- @implementation UINavigationController (FDFullscreenPopGesture)
- + (void)load
- {
- // Inject "-pushViewController:animated:"
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- Class class = [self class];
-
- SEL originalSelector = @selector(pushViewController:animated:);
- SEL swizzledSelector = @selector(fd_pushViewController:animated:);
-
- Method originalMethod = class_getInstanceMethod(class, originalSelector);
- Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
-
- BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
- if (success) {
- class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
- } else {
- method_exchangeImplementations(originalMethod, swizzledMethod);
- }
- });
- }
- - (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
- {
- if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
-
- // Add our own gesture recognizer to where the onboard screen edge pan gesture recognizer is attached to.
- [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];
-
- // Forward the gesture events to the private handler of the onboard gesture recognizer.
- NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
- id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
- SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
- self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
- [self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction];
-
- // Disable the onboard gesture recognizer.
- self.interactivePopGestureRecognizer.enabled = NO;
- }
-
- // Handle perferred navigation bar appearance.
- [self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
-
- // Forward to primary implementation.
- if (![self.viewControllers containsObject:viewController]) {
- [self fd_pushViewController:viewController animated:animated];
- }
- }
- - (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
- {
- if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) {
- return;
- }
-
- __weak typeof(self) weakSelf = self;
- _FDViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (strongSelf) {
- [strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
- }
- };
-
- // Setup will appear inject block to appearing view controller.
- // Setup disappearing view controller as well, because not every view controller is added into
- // stack by pushing, maybe by "-setViewControllers:".
- appearingViewController.fd_willAppearInjectBlock = block;
- UIViewController *disappearingViewController = self.viewControllers.lastObject;
- if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
- disappearingViewController.fd_willAppearInjectBlock = block;
- }
- }
- - (_FDFullscreenPopGestureRecognizerDelegate *)fd_popGestureRecognizerDelegate
- {
- _FDFullscreenPopGestureRecognizerDelegate *delegate = objc_getAssociatedObject(self, _cmd);
-
- if (!delegate) {
- delegate = [[_FDFullscreenPopGestureRecognizerDelegate alloc] init];
- delegate.navigationController = self;
-
- objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- return delegate;
- }
- - (UIPanGestureRecognizer *)fd_fullscreenPopGestureRecognizer
- {
- UIPanGestureRecognizer *panGestureRecognizer = objc_getAssociatedObject(self, _cmd);
-
- if (!panGestureRecognizer) {
- panGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
- panGestureRecognizer.maximumNumberOfTouches = 1;
-
- objc_setAssociatedObject(self, _cmd, panGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- return panGestureRecognizer;
- }
- - (BOOL)fd_viewControllerBasedNavigationBarAppearanceEnabled
- {
- NSNumber *number = objc_getAssociatedObject(self, _cmd);
- if (number) {
- return number.boolValue;
- }
- self.fd_viewControllerBasedNavigationBarAppearanceEnabled = YES;
- return YES;
- }
- - (void)setFd_viewControllerBasedNavigationBarAppearanceEnabled:(BOOL)enabled
- {
- SEL key = @selector(fd_viewControllerBasedNavigationBarAppearanceEnabled);
- objc_setAssociatedObject(self, key, @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- @end
- @implementation UIViewController (FDFullscreenPopGesture)
- - (BOOL)fd_interactivePopDisabled
- {
- return [objc_getAssociatedObject(self, _cmd) boolValue];
- }
- - (void)setFd_interactivePopDisabled:(BOOL)disabled
- {
- objc_setAssociatedObject(self, @selector(fd_interactivePopDisabled), @(disabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- - (BOOL)fd_prefersNavigationBarHidden
- {
- return [objc_getAssociatedObject(self, _cmd) boolValue];
- }
- - (void)setFd_prefersNavigationBarHidden:(BOOL)hidden
- {
- objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- - (CGFloat)fd_interactivePopMaxAllowedInitialDistanceToLeftEdge
- {
- #if CGFLOAT_IS_DOUBLE
- return [objc_getAssociatedObject(self, _cmd) doubleValue];
- #else
- return [objc_getAssociatedObject(self, _cmd) floatValue];
- #endif
- }
- - (void)setFd_interactivePopMaxAllowedInitialDistanceToLeftEdge:(CGFloat)distance
- {
- SEL key = @selector(fd_interactivePopMaxAllowedInitialDistanceToLeftEdge);
- objc_setAssociatedObject(self, key, @(MAX(0, distance)), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- @end
|