现象
最近在我们的 App 中遇到了一个奇葩的问题,在某种场景下,App 会处于假死的状态。
现象:点击屏幕上的任何位置都没有反应,按 Home 键后再打开 App 才能正常
在一个不经意的操作中,终于发现了复现规律,当进入到某个特定的页面后,再 pop 回到 UINavigationController 的第一级时,此时如果在屏幕边缘右滑,问题就出现了。
在我们的 App 中,这个特定的页面是一个包含有 WKWebView 的 ViewController,当时我们模仿了微信在左上角自定义了返回按钮和关闭按钮。由于自定义了 leftBarButtons,所以屏幕边缘右滑返回的功能就失效了。我当时理所当然地在这个页面中添加了以下代码
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIScreenEdgePanGestureRecognizer, !self.webView.canGoBack {
return true
}
return false
}
当我以为优雅地解决了右滑返回的问题时,却发现引入了这么严重的一个问题。。。
解决方案?
于是我 Google 了一下,发现不少人都给出同一个建议,看下面代码
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
let isRoot = viewController == navigationController.viewControllers.first
navigationController.interactivePopGestureRecognizer?.isEnabled = !isRoot
}
问题是能解决,但是彻底解决问题了吗?
这个方案是去接管 UINavigationController
的 delegate
,当在 UINavigationController
的第一级时关闭屏幕边缘右滑功能,反之则开启。
但大家是否有想过这样的问题:
- 一般的 iOS App 都有几个 Tab(我们的 App 就有三个 Tab),这就意味着每一个 Tab 的
UINavigationController
都要去做这个处理,万一哪天增加了一个 Tab 但没有做这个处理,那问题同样会遇上。 - App 中也经常会做 present 操作,而 present 出来的 ViewController 一般也会在 UINavigationController 里面,同样要处理,否则还会遇上这个问题。
这样才是正解
想到上面两个场景,不寒而栗,另谋其它解决方案!
回想一下问题出现的过程,如果不进入到那个特定的 ViewController,在屏幕边缘右滑是没有问题的,而进入到特定页面再出来问题就出现了,为什么这么奇怪?
由于改的代码量很少,很容易就关注到下面这个地方
navigationController?.interactivePopGestureRecognizer?.delegate
我修改过 delegate
对象,导致问题出现,那原来的 delegate
对象必然不是空的,并且原来的 delegate
对象保证了即使在 UINavigationController
的第一级右滑也不会有假死的问题,所以完美的解决方案其实很简单,先持有原 delegate 对象,最后再修改回去
weak var originalGestureDelegate: UIGestureRecognizerDelegate?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.originalGestureDelegate = self.navigationController?.interactivePopGestureRecognizer?.delegate
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.interactivePopGestureRecognizer?.delegate = self.originalGestureDelegate
}
好奇
很好奇,原来的 delegate
对象到底是什么?
打个断点即可看到是 UINavigationInteractiveTransitionBase
的对象,这里来找到了个很好玩的东西,可以看看这篇文章
要养成一个好习惯
要养成一个好习惯,修改一个级的属性时,一定要先持有这个属性后,再赋值,最终要修改回来
这样说可能有点绕,大家平时在做一些界面的时候,某些界面要隐藏掉导航栏,此时可不能粗暴地在这个页面中将导航栏隐藏掉,正确的做法应该跟上面的解决方案一样,在 viewWillAppear
先持有上一个界面的导航栏状态,在 viewWillDisappear
修改回原来的状态
好慷在家
广告时间
欢迎大家来下载我们的 App 好慷在家,提供专业的保洁保姆服务,38节活动进行中,买份家务包年,宠爱自己一整年!
好慷38节活动都看完了,不点个赞就走,这样是耍流氓知道吗?👮🏻
网友评论