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
网友评论