iOS pop push UINavigationController
相信现如今大多数应用的架构属于主流架构,即在一个UITabBarController
里面添加多个UINavigationController
,每一个UINavigationController
里面又添加多个UIViewController
或者其子类。
在从次级界面开始,每个UIViewController
中,iOS系统默认在左侧边缘,用手指向右滑动,就可以pop到上一级界面,如果不是很细致的人,绝对是想不到有这功能的,如果使用过的人,想必也对这个功能感觉有些鸡肋,那么是否可以把这个手势添加到全屏,使之可以从全屏任意右滑,以实现全屏pop的功能呢?
答案当然是肯定的,以下是实现思路:
- 实现全屏pop我有两种思路,而且也曾都实现,第一种思路是,给
UINavigationController
添加子类,给view添加拖动手势UIPanGestureRecognizer
,这样做比较麻烦,计算过多,不是很好的一种选择。- 另一种思路是通过KVC获取
UINavigationController
成员属性interactivePopGestureRecognizer
的私有属性,获得target
和action
,这两个属性的值。- 如果获得上面两个私有属性对应的值,然后传入到
UIPanGestureRecognizer(target: target, action: action)
自定义拖拽手势中,就可以实现了- 那么如何获取私有属性呢?这就是我们已经比较常见的
runtime
即运行时了
实现
由runtime
,我们可以获取某个类的成员变量:
var count: UInt32 = 0
let ivars = class_copyIvarList(UIGestureRecognizer.self, &count)
for i in 0..<count {
guard let ivar = ivars![Int(i)] else {
continue
}
let nameP = ivar_getName(ivar)
let name = String(cString: nameP!)
print(name)
}
打印之后,我们就会看到有一个成员变量为_targets
,那么这个成员变量是否是interactivePopGestureRecognizer
的私有变量呢?那么就用KVC尝试下:
guard let targetsValue = interactivePopGestureRecognizer?.value(forKeyPath: "_targets") as? [NSObject] else {
return
}
print(targetsValue)
打印之后,我们会得到:
[(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fa44df158c0>)]
6
这貌似是我们需要的结果!那么就获得他们,转为我们熟悉的对象:
// 取出对象
guard let targetObjc = targetsValue.first else {
return
}
// 从对象中取出target 和 action
let target = targetObjc.value(forKeyPath: "target")
let action = targetObjc.value(forKeyPath: "action") as? Selector
// 创建自己的手势
let pan = UIPanGestureRecognizer(target: target, action: action)
view.addGestureRecognizer(pan)
运行,结果是让人失望的,程序直接奔溃,打印如下:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIGestureRecognizerTarget 0x608000220e00> valueForUndefinedKey:]: this class is not key value coding-compliant for the key action.
很显然是因为没有action这个key,但我们在打印中明显看到有action的,那是什么原因呢?答案我猜是系统重写了description方法!既然如此,但其value实不会变的,那么何不直接复制其方法使用呢?
guard let targetsValue = interactivePopGestureRecognizer?.value(forKeyPath: "_targets") as? [NSObject] else {
return
}
guard let targetObjc = targetsValue.first else {
return
}
// 从对象中取出target 和 action
let target = targetObjc.value(forKeyPath: "target")
// 只有直接复制 函数 包装成selector
let action = Selector(("handleNavigationTransition:"))
// 创建自己的手势
let pan = UIPanGestureRecognizer(target: target, action: action)
view.addGestureRecognizer(pan)
如此,就可以轻松愉快地实现了全屏pop功能了,是不是很简单呢?看起来是很简单,其实也不容易,在这个过程中,更重要的是一种思想,有时候使用runtime
就可以很轻松实现我们需要的功能。