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

FDFullscreenPopGesture源码阅读笔记

作者: 我是小胡胡123 | 来源:发表于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