FDFullScreenPopGesture
是一个非常小巧精悍,却又功能强大的开源项目。主要的功能有:全局的手势回退;部分界面的 NavigationBar
的隐藏。今天花了半天的时间搞明白了其中的原理。
iOS 原生的返回手势的起始滑动点必须靠近屏幕的左方,无法实现全屏返回。我们可以给控制器的 view 添加手势来控制它的 frame。但是那样需要自己控制动画,自己控制 NavigationBar
,非常麻烦。FDFullScreenPopGesture
通过一种优雅的方式解决了这个问题。并且顺带实现了部分控制器隐藏 NavigationBar
的功能。
总体解构
整个 FDFullScreenPopGesture
总共分为四个部分:
_FDFullscreenPopGestureRecognizerDelegate
UIViewController (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
,如果自己找起来的话,真的困难。