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;
}
}