iOS 13.4 UINavigationController
背景
iOS 13.4 正式版正式推出不久后,有用户反馈 app 的一些手势返回出现问题,比如从发现页的搜索返回 Tabbar 消失,在书架界面触发 pop 手势后无法再 push 任何页面,经过排查发现是 iOS 13.4 一些改动造成的。
image.gif系统行为
先来回顾一下 UINavigationController pop 手势的一些系统的默认行为,大部分情况下,系统的允许用户触发 pop 手势,除了以下几种情况:
- 当前位于 rootViewController
- 当前 viewController 隐藏了 NavigationBar
- 当前 viewController 设置了
leftBarButtonItem
或者leftBarButtonItems
对于系统的三个限制行为,是通过 navigationController.interactivePopGestureRecognizer
的 delegate 来控制是否允许触发 pop 手势。
在 iOS 13.4 之前,由 interactivePopGestureRecognizer.delegate 中的
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
方法实现实现上述行为的控制,如果不修改 delegate 指向,会按照系统的实现,但是由于业务的需求,通常要在第 2、3 点的情况下,仍然响应手势的返回,因此,许多业务为了去掉系统的限制,会修改 interactivePopGestureRecognizer 的 delegate 的指向,或者干脆清空 delegate。这样做无论在什么情况下都可以触发 pop 手势(只要不实现 gestureRecognizer:shouldReceiveTouch: 默认按照可以触发 pop 手势处理),显然这样是不够严谨的,原因如下:
1、失去了对第一点的限制会触发一个bug,即在 rootViewController 触发了 pop 手势,这样会导致下次无法正常 push,为了解决这个问题,新的 delegate 可能需要做以下的实现:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (self.viewControllers.count > 1) {
return YES;
}
return NO;
}
2、可能是某个 viewControler 需要去掉第 2、3 点的限制,但是一旦修改了 delegate 会影响后续整个 NavigationController 栈上的所有控制器,导致容易出现一些不符合预期的问题。
QMUI 针对 pop 手势的处理
QMUI 框架封为了解决上述问题,做了一些封装处理,实现了在业务在不修改 delegate 的情况下,可以通过实现 - (BOOL)forceEnableInteractivePopGestureRecognizer;
, 来控制系统第 2、3 点的限制,对于同一个 NavigationController 的不同 viewControler 可以选择实现该方法来打破系统的限制,或者不实现继续沿用系统的限制。
QMUI 的实现原理是在 UINavigationController 的 viewDidLoad 时,将原始的 interactivePopGestureRecognizer 存起来,然后将 delegate 指向自己,并且在
gestureRecognizer:shouldReceiveTouch: 返回原始 delegate 的返回值。如果业务实现了 forceEnableInteractivePopGestureRecognizer 则以业务的优先级为准。
iOS 13.4 上的变化
好了讲了这么多,到底 iOS 13.4 有什么变化呢,变化就是 iOS 13.4 系统的 delegate 不再通过 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
这个方法来控制上述的三点限制,而是改用了一个私有方法代替以前的方法:
- (BOOL)_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveEvent:(UIEvent *)event;
如果修改了 delegate,按照以前的实现,在 gestureRecognizer:shouldReceiveTouch: 返回原始 delegate 以前的结果,会导致上三个限制完全失效(因为在 iOS 13.4 之后原始 delegate 该方法的值永远为 YES,即可以响应手势事件),出现一些原本系统不允许 pop 手势返回,现在表现为允许。比如文章开头一开始提到的书城返回 Tabbar 消失是因为:
发现页的搜索是采用的自定义 NavigationBar 并隐藏了系统的 NavigationBar,符合第 2 点限制,系统默认是不允许触发 pop 手势的,这个地方业务又自己实现了一个手势,只有触发业务的手势才能正确返回,但是在 iOS13.4 上,由于 QMUI 没有实现新的私有方法,导致了第 3 点限制被取消,触发了系统的 pop 手势,而业务之前隐藏了 Tabbar,并且没在针对系统的 pop 手势把 Tabbar 显示回来,导致了 BUG 的出现。
而书架的问题是因为失去了第 1 点限制导致了系统的 BUG 出现。
修复方法:
1、实现私有方法,并返回原始 delegate 中的值。
2、如果不想用私有方法,可以在 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
方法中自行实现上述三点判断。