美文网首页
FDFullscreenPopGesture源码阅读笔记

FDFullscreenPopGesture源码阅读笔记

作者: 我是小胡胡分胡 | 来源:发表于2018-06-23 17:53 被阅读263次

    1, hook

    viewWillAppear
    viewWillDisappear

    fd_pushViewController

    2,注入

    push的时候,
    appearingViewController
    disappearingViewController (push之前的导航控制器容器中的第0个就是)
    这两种vc 都注入了block

    block干的事情就是调用代码显示or隐藏导航条

    [strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
    强制去动画--什么效果呢?
    1、效果就是, 当页面滑到一半,如果willAppear的页面的导航条是隐藏的,就立即隐藏,如果是显示的就立即显示。 会导致当前滑动的一般页面就立即显示导航条的这个有无
    setNavigationBarHidden:animated: NO

    2、如果加上了动画,就不会立即,而是等待页面真正全部进去之后,才会改变导航条。这种效果会稍稍好看一点
    setNavigationBarHidden:animated: YES

    3、navigationBar.hidden 会立即改变导航条。
    即使手动加入动画执行,还是会立即变化。导致过度时候跟当前页不匹配

    [strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:NO];
    [UIView animateWithDuration:0.15 animations:^{
    strongSelf.navigationBar.hidden=viewController.fd_prefersNavigationBarHidden;
    }];

    所以,最佳的情况就是直接设置setNavigationBarHidden:animated:方法

    3, push的时机,给导航控制器的uiview的系统自带的interactivePopGestureRecognizer换成新的手势,并把自带的手势的target和action,由新加的手势来趋动。

    image.png

    模拟这一过程,发现替换不起作用啊

    @interface ViewController ()<UIGestureRecognizerDelegate>
    @property (strong, nonatomic) IBOutlet UIPanGestureRecognizer *gestureAction;
    
    @property (weak, nonatomic) IBOutlet UIScrollView *topScrollView;
    @property (weak, nonatomic) IBOutlet UIScrollView *bottomScrollView;
    @property (strong, nonatomic)     UIGestureRecognizer* topPanGesture;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        SEL action = NSSelectorFromString(@"handlePan:");
        
        void (^block)(UIScrollView*scrollView,id *panGesture, id *target)=^(UIScrollView*scrollView, id *panGesture, id*target){
            
            * panGesture=nil;
            * target =nil;
            
            for (UIGestureRecognizer*gesture in scrollView.gestureRecognizers) {
                NSLog(@"😊  %@  \n\n", gesture);
                if ([gesture isKindOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")]) {
                    *panGesture =gesture;
                    //打印出来的是target, 实际上是一个targets,是一个数组
                    NSArray* targets=[*panGesture valueForKey:@"targets"];
                    //UIGestureRecognizerTarget类型的,不是字典类型的
                    NSDictionary*targetDict=[targets firstObject];
                    *target = [targetDict valueForKey:@"target"];
                    // action = NSSelectorFromString(@"handlePan:");
                    
                    break;
                }
            }
        };
        
        UIGestureRecognizer* topPanGesture;
        id topTarget;
        id bottomPanGesture;
        id bottomTarget;
        block(self.topScrollView, &topPanGesture,&topTarget);
        //block(self.bottomScrollView, &bottomPanGesture,&bottomTarget);
    //    [bottomPanGesture addTarget:topTarget action:action];
    //    topPanGesture.enabled=NO;
        self.topPanGesture=topPanGesture;
        [topPanGesture.view addGestureRecognizer:self.gestureAction];
    //用新加的手势来驱动,不起作用??
        [self.gestureAction addTarget:topTarget action:action];
     //   [self.gestureAction addTarget:self action:@selector(action2:)];
        self.gestureAction.delegate=self;
    //     self.topPanGesture.enabled=NO;
        NSLog(@"🎾, %s", __func__);
    }
    -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
        NSLog(@"🎾, %s", __func__);
        return YES;
    }
    -(void)action2:(id)sender{
        NSLog(@"🎾, %s", __func__);
    }
    -(void)viewDidAppear:(BOOL)animated{
        [super viewDidAppear:animated];
      //  NSLog(@"🎾, %s", __func__);
        self.topPanGesture.enabled=NO;
    }
    
    /**
     UIScrollView 4个手势,
     1、target 是scrollview自身,
     2、除了_UIDragAutoScrollGestureRecognizer的代理是空,剩下3个的代理也是scrollView自身
     
     UIScrollViewDelayedTouchesBeganGestureRecognizer
     UIScrollViewPanGestureRecognizer
     UIScrollViewPagingSwipeGestureRecognizer 代理也是自己
     _UIDragAutoScrollGestureRecognizer 代理是nil
     
     
     3、must-fail  / must-fail-for 是成对的
     UIScrollViewPanGestureRecognizer,UIScrollViewPagingSwipeGestureRecognizer 相互排斥
     
     */
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    @end
    
    
    #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];
        //凡是从控制器push过的,都被注入了block,在willAppear中就会执行这个block
        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(), ^{
            
            //被延迟执行,新的vc调用willappear之后,旧的vc的navigationController就为nil了
            //而此时的self指的是旧的vc, 所以下面几行代码实则无用
            UIViewController *viewController = self.navigationController.viewControllers.lastObject;
            
            NSLog(@"fd_viewWillDisappear  %p barhidden %d self %p nav %p",viewController,viewController.fd_prefersNavigationBarHidden, self,self.navigationController);
            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);
            }
        });
    }
    //  只有是经过push的控制器,才会给  1、push进来的  和2、执行push的  那些个vc控制器 注入willAppear block
    // WillAppear是在原有的willAppear执行后才执行的,也就是说这里的hook的会覆盖那些设置
    // 设置什么呢?setNavigationBarHidden:animated:
    // 给导航控制器的rootUIView上的interactivePopGestureRecognizer手势,取其target和action给新创建的手势,原有的手势禁用掉
    - (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
        //self.interactivePopGestureRecognizer.view 就是navgiationController的view,这个view就是--UILayoutContainerView
        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.
            //把系统的侧滑手势的target和action取出来
            NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
            id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
            SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
            //创建手势,设置代理
            self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
            //手势增加target-action
            [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) {
            NSLog(@"fd_viewWillAppear  %p barhidden %d",viewController,viewController.fd_prefersNavigationBarHidden);
            __strong typeof(weakSelf) strongSelf = weakSelf;
            if (strongSelf) {
                [strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
                
                //强制去动画--什么效果呢?
                //效果就是, 当页面滑到一半,如果willAppear的页面的导航条是隐藏的,就立即隐藏,如果是显示的就立即显示。 会导致当前滑动的一般页面就立即显示导航条的这个有无
                //如果加上了动画,就不会立即,而是等待页面真正全部进去之后,才会改变导航条。这种效果会稍稍好看一点
                //[strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:NO];
                // [UIView animateWithDuration:0.15 animations:^{
                //即使手动加入动画执行,也是会立即改变导航条。 所以,最佳的情况就是直接设置setNavigationBarHidden:animated:方法
                //   strongSelf.navigationBar.hidden=viewController.fd_prefersNavigationBarHidden;
                // }];
            }
        };
        
        // 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:".
        //不是所有的控制器都是push进来的,initWithRootVc就不会调用,这里需要将这种情况下的控制器,需要把它补上
        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
    
    
    

    相关文章

      网友评论

          本文标题:FDFullscreenPopGesture源码阅读笔记

          本文链接:https://www.haomeiwen.com/subject/gqldyftx.html