解决pop手势失效的方法
系统的UINavigationController有一个pop手势,只要手指在屏幕左边滑动,当前的视图控制器的视图会随着手指移动,当手指离开屏幕时,系统会自动判断是否执行pop操作。
但是自定义左边按钮或隐藏了导航栏后,手势会失效。
要解决手势失效的问题,要先研究pop手势。
@available(iOS 7.0, *)
open var interactivePopGestureRecognizer: UIGestureRecognizer? { get }
pop手势是在iOS7.0的时候添加的,是UIGestureRecognizer?类型的只读属性。
先打印一下看看:
<UIScreenEdgePanGestureRecognizer: 0x7ff22ff0ea30; state = Possible;
delaysTouchesBegan = YES; view = <UILayoutContainerView
0x7ff22ff0d810>; target= <(action=handleNavigationTransition:,
target=<_UINavigationInteractiveTransition 0x7ff22ff0e8f0>)>>
再打印一下pop手势的delegate:
<_UINavigationInteractiveTransition: 0x7ff22ff0e8f0>
可以发现pop手势的target和delegate都是<_UINavigationInteractiveTransition: 0x7ff22ff0e8f0>,_UINavigationInteractiveTransition是一个私有类,看不到头文件,不过可以通过
@available(iOS 2.0, *)
public func class_copyMethodList(_ cls: AnyClass?, _ outCount: UnsafeMutablePointer<UInt32>?) -> UnsafeMutablePointer<Method>?
获取到类的方法列表。如下所示:
.cxx_destruct
gestureRecognizer:shouldReceiveTouch:
gestureRecognizerShouldBegin:
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
startInteractiveTransition
screenEdgePanGestureRecognizer
_gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:
initWithViewController:animator:
_setShouldReverseLayoutDirection:
setNotInteractiveTransition
gestureRecognizerView
_configureNavigationGesture
可以发现_UINavigationInteractiveTransition实现了UIGestureRecognizerDelegate的三个方法,这几个方法是控制pop手势有效和失效的关键。
先写一个类观察一下_UINavigationInteractiveTransition的行为。
open class NavigationInteractiveTransition: NSObject {
public private(set) weak var navigationController: UINavigationController?
public let wrapped: UIGestureRecognizerDelegate?
public init(_ navigationController: UINavigationController) {
self.navigationController = navigationController
self.wrapped = navigationController.interactivePopGestureRecognizer?.delegate
}
}
extension NavigationInteractiveTransition: UIGestureRecognizerDelegate {
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let result = wrapped?.gestureRecognizerShouldBegin?(gestureRecognizer) {
print("begin", result)
return result
}
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let result = wrapped?.gestureRecognizer?(gestureRecognizer, shouldReceive: touch) {
print("touch", result)
return result
}
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let result = wrapped?.gestureRecognizer?(gestureRecognizer, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) {
print("simultaneously", result)
return result
}
return true
}
}
把navigationController.interactivePopGestureRecognizer?.delegate替换为NavigationInteractiveTransition类的对象,多操作几次,可以发现
- 在根视图控制器时,不接受touch
- 不接受UINavigationBar上的touch
- 当自定义左边按钮或隐藏UINavigationBar时,不接受touch
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if navigationControllerShouldPop() == false {
return false
}
if isTouchViewInNavigationBar(touch.view) {
return false
}
return true
}
open func navigationControllerShouldPop() -> Bool {
guard let navigationController = self.navigationController else {
return false
}
if navigationController.viewControllers.count <= 1 {
return false
}
return true
}
open func isTouchViewInNavigationBar(_ view: UIView?) -> Bool {
guard let navigationController = self.navigationController else {
return false
}
var temp = view
while temp != nil {
if navigationController.navigationBar.isEqual(temp) {
return true
}else {
temp = temp?.superview
}
}
return false
}
到这里,已经解决了pop手势失效的问题了。
UIViewController决定是否可以pop
有时候会有这样的需求,有的页面不想让pop手势起作用。先定义一个协议InterruptNavigationControllerPopGesture。
public protocol InterruptNavigationControllerPopGesture {
func interruptNavigationController(_ navigationController: UINavigationController, PopGesture: UIGestureRecognizer) -> Bool
}
修改NavigationInteractiveTransition的代码。
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if navigationControllerShouldInterruptPopGesture(gestureRecognizer) {
return false
}
if let result = wrapped?.gestureRecognizerShouldBegin?(gestureRecognizer) {
return result
}
return true
}
open func navigationControllerShouldInterruptPopGesture(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let navigationController = navigationController else {
return true
}
guard let object = navigationController.topViewController as? InterruptNavigationControllerPopGesture else {
return false
}
return object.interruptNavigationController(navigationController, PopGesture: gestureRecognizer)
}
让不想pop手势有效的类遵守协议InterruptNavigationControllerPopGesture,实现协议方法返回true打断UINavigationController的pop手势。
Demo见github