FDFullScreenPopGesture 是一个非常小巧精悍,却又功能强大的开源项目。主要的功能有:全局的手势回退;部分界面的 NavigationBar 的隐藏。今天花了半天的时间搞明白了其中的原理。
iOS 原生的返回手势的起始滑动点必须靠近屏幕的左方,无法实现全屏返回。我们可以给控制器的 view 添加手势来控制它的 frame。但是那样需要自己控制动画,自己控制 NavigationBar ,非常麻烦。FDFullScreenPopGesture 通过一种优雅的方式解决了这个问题。并且顺带实现了部分控制器隐藏 NavigationBar 的功能。
总体解构
整个 FDFullScreenPopGesture 总共分为四个部分:
_FDFullscreenPopGestureRecognizerDelegateUIViewController (FDFullscreenPopGesturePrivate)UIViewController (FDFullscreenPopGesture)UINavigationController (FDFullscreenPopGesture)
_FDFullscreenPopGestureRecognizerDelegate
这个类只有一个方法,就是实现了 UIGestureRecognizerDelegate 的代理方法 gestureRecognizerShouldBegin:。用来控制是否开始手势操作。
1 | - (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer |
总共在5中情况下,不能全屏幕返回:
- 整儿
NavigationController中只有一个ViewController的时候 - 当前的
ViewController禁用了fd_interactivePopDisabled(这个在UINavigationController (FDFullscreenPopGesture)中设置的) - 当前手势的启动点距离左边缘远过了
fd_interactivePopMaxAllowedInitialDistanceToLeftEdge(同上,也是自己设置的) - 当前是否在转场过程中。这里通过 KVC 拿到了
NavigationController中的_isTransitioning属性 - 手势运动方向是否是从右往左
UIViewController (FDFullscreenPopGesturePrivate)
如果你对 runtime 比较熟悉,那么看到这个类一定非常亲切。这个方法在 load 方法的时候,通过 Method Swizzling 用 fd_viewWillAppear: 方法,替换了原来的 viewWillAppear: 方法。
1 | - (void)fd_viewWillAppear:(BOOL)animated |
这个替换的方法没什么特别的,就是在其中插入了自己的回调块 fd_willAppearInjectBlock ,该块在 UINavigationController (FDFullscreenPopGesture) 中定义并设置。该块用来设置 NavigationBar 的显示与隐藏。
UIViewController (FDFullscreenPopGesture)
这个类中定义了3个属性,上面用到了两个 fd_interactivePopDisabled,fd_interactivePopMaxAllowedInitialDistanceToLeftEdge 还有一个 fd_prefersNavigationBarHidden 属性用于判断是否隐藏 NavigationBar 上面的 fd_willAppearInjectBlock 中用到。
1 | @interface UIViewController (FDFullscreenPopGesture) |
UINavigationController (FDFullscreenPopGesture)
这个方法是整个项目的逻辑的核心,首先还是在 load 方法中用 fd_pushViewController:animated: 方法替换了系统自身的 pushViewController:animated: 方法。该方法如下:
1 | - (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated |
先看第一步,在 interactivePopGestureRecognizer 的 view 上添加一个 UIPanGestureRecognizer。这个 interactivePopGestureRecognizer 是什么呢? 这是控制界面在边缘处滑动返回的手势。这是一个只读的属性,我们当然不能直接设置它,拿到它是为了通过它拿到该手势所在的 view,在 view 中保存了一个 _gestureRecognizers 的数组。试想一下,如何判断是否需要 pop 呢?就是通过拿到 view 中的 _gestureRecognizers ,再调用每个手势的 UIGestureRecognizerDelegate 中的 gestureRecognizerShouldBegin方法是否返回yes。这里苹果之所以把_gestureRecognizers` 设置为数组,可能是为了以后更易拓展吧。当然,这也方便了我们的拓展。
第二步将自己添加的手势设置成与原生手势相同,包括一个 target 和一个 action,这样两个手势的动画以及触发方法就一模一样了。
第三步将原来的手势禁用。
第四步设置上面说过的 _FDViewControllerWillAppearInjectBlock 。也就是通过 fd_prefersNavigationBarHidden 来显示和隐藏 NavigationBar。
总结
主要复习了一下 method swizzling 的使用,可以很优雅地修改特定的方法。
另外,虽然苹果没开源,但还是通过获取到了系统类中的私有方法。这给我们提供了一个思路。我们也可以通过调试,来查看各个隐藏的变量。不过有一点要小心:系统类内定义的一些 property 只是定义同名的 get set 方法,但是实际存储的变量名并不和 property 名相同,变量存储的位置也不能完全确定。比如上面 UINavigationController 的 interactivePopGestureRecognizer 属性,被保存在 UINavigationController 中的 __cachedInteractionController 之下,名为 _edgePanRecognizer,如果自己找起来的话,真的困难。