美文网首页
FDFullScreenPopGesture源码解析

FDFullScreenPopGesture源码解析

作者: 纯情_小火鸡 | 来源:发表于2017-08-11 10:37 被阅读269次

    FDFullScreenPopGesture仅300行不到的代码就完美实现了丝滑的全屏滑动,如果是我们自己实现一个全屏滑动而且还能保证UINavigationBar良好的切换效果你会怎么做呢?

    1.最简单的则是在返回的VC中写下如下代码:

    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        [self.navigationController setNavigationBarHidden:YES animated:animated];
    }
    

    当然,如果场景不多还是可取,需要将其隐藏之后,在合适的时机又显示出来,场景一多可能就容易出乱子了

    2.直接隐藏系统的UINavigationBar,自己去实现一个View放在顶部,这种就是完全可自定义,不过花费代价也是稍大一些

    3.使用截图将前一个界面的视图保存起来,自定义手势,在滑动时判断并显示,这个需要处理的逻辑和情况也相对复杂,因为push和pop操作需要可以pop到上一级或者指定或者根视图控制器等

    4.实现UIViewControllerAnimatedTransitioning协议,自定义转场动画,在熟悉的情况下是可行的

    5.今天的主角,FDFullScreenPopGesture,完全解耦合,只需要拖入工程中就能实现该效果。那么这么好的东西他是如何实现的呢?下面我们来看一下👇

    工程结构:

    • UINavigationController (FDFullscreenPopGesture)pushViewController:animated的hook

    • UIViewController (FDFullscreenPopGesture):主要进行viewWillAppear的hook

    • _FDFullscreenPopGestureRecognizerDelegate:负责管理UIGestureRecognizerDelegate的代理

    • UIViewController (FDFullscreenPopGesture):通过runtime添加几个设置属性

    代码解析:

    _FDFullscreenPopGestureRecognizerDelegate

    //实现了UIGestureRecognizerDelegate的gestureRecognizerShouldBegin代理方法,对手势的操作进行管理
    - (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
    {
        //当navigationController中只有一个ViewController时返回NO,该手势不生效
        if (self.navigationController.viewControllers.count <= 1) {
            return NO;
        }
        
        //当前的 ViewController 禁用了 fd_interactivePopDisabled,fd_interactivePopDisabled是使用objc_getAssociatedObject在类别中添加的一个属性,用户可设置是否禁用
      UIViewController *topViewController = self.navigationController.viewControllers.lastObject;
        if (topViewController.fd_interactivePopDisabled) {
            return NO;
        }
        //同理设置了一个fd_interactivePopMaxAllowedInitialDistanceToLeftEdge属性用于设置最大左边距,当滑动的x坐标大于他时也是无效的
        CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
        CGFloat maxAllowedInitialDistance = topViewController.fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
        if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
            return NO;
        }
    
        //当前是否在转场过程中。这里通过 KVC 拿到了 NavigationController 中的私有 _isTransitioning 属性
        if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
            return NO;
        }
        
        // 从右往左滑动也是无效的
        CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
        if (translation.x <= 0) {
            return NO;
        }
        
        return YES;
    }
    

    UIViewController (FDFullscreenPopGesturePrivate)

    typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);//定义了一个block用于在Swizzling的fd_viewWillAppear方法中回调
    
    //在整个文件被加载到运行时,在 main 函数调用之前被 ObjC 运行时调用的钩子方法
    + (void)load
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
            
            SEL originalSelector = @selector(viewWillAppear:);
            SEL swizzledSelector = @selector(fd_viewWillAppear:);
            
            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_addMethod 方法返回 YES 。这时使用 class_getInstanceMethod 函数获取到的 originalSelector 指向的就是父类的方法,我们再通过执行 class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 将父类的实现替换到我们自定义的 mrc_viewWillAppear 方法中。这样就达到了在 mrc_viewWillAppear 方法的实现中调用父类实现的目的。
                class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
            } else {
              //主类本身有实现需要替换的方法,也就是 class_addMethod 方法返回 NO 。这种情况的处理比较简单,直接交换两个方法的实现就可以了
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    - (void)fd_viewWillAppear:(BOOL)animated
    {
        // Method Swizzling之后, 调用fd_viewWillAppear:实际执行的代码已经是原来viewWillAppear中的代码了
        [self fd_viewWillAppear:animated];
        
        if (self.fd_willAppearInjectBlock) {
            self.fd_willAppearInjectBlock(self, animated);
        }
    }
    
    - (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
    {
      //_cmd在Objective-C的方法中表示当前方法的selector,正如同self表示当前方法调用的对象实例
        return objc_getAssociatedObject(self, _cmd);
    }
    
    - (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
    {
        objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    

    UINavigationController (FDFullscreenPopGesture)

    //将此方法替换了pushViewController:animated:
    - (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
      
        if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
            
            // 将自定义的UIPanGestureRecognizer添加到本来interactivePopGestureRecognizer所在的view上
            [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];
    
            // 使用kvc拿到内部的targets数组,并找到他的target和action SEL,将fd_fullscreenPopGestureRecognizer的target设置为internalTarget,action设置为handleNavigationTransition.实际上就是将系统的手势事件转发为自定义的手势,触发的事件不变,厉害吧,能找到这些属性也是牛逼炸了
            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];
    
            // 将原来的手势禁用
            self.interactivePopGestureRecognizer.enabled = NO;
        }
        
        // 通过 fd_prefersNavigationBarHidden 来显示和隐藏 NavigationBar
        [self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
        
        //调用父类pushViewController:animated
        if (![self.viewControllers containsObject:viewController]) {
            [self fd_pushViewController:viewController animated:animated];
        }
    }
    
    
    - (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
    {
        //如果设置属性为NO,即为隐藏
        if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) {
            return;
        }
        
        //viewWillAppear的时候将会调用该方法,实际上内部也是通过调用setNavigationBarHidden:animated:来设置NavigationBar的显示
        __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];
            }
        };
        
        // 将fd_willAppearInjectBlock注入到新的的view controller中
        // 将栈顶的viewController拿出来并且判断是否已经注入了fd_willAppearInjectBlock,没有则添加,因为并不一定每个vc都是通过push加入到栈的,也有可能通过"-setViewControllers:"
        appearingViewController.fd_willAppearInjectBlock = block;
        UIViewController *disappearingViewController = self.viewControllers.lastObject;
        if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
            disappearingViewController.fd_willAppearInjectBlock = block;
        }
    }
    

    相关文章

      网友评论

          本文标题:FDFullScreenPopGesture源码解析

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