回顧
在 iOS – 實現UINavigation全屏滑動返回(一) 中我們實現了滑動返回的功能,但不是全屏滑動返回,得在左側邊緣輕掃才能滑動返回~UINavigationController自帶的只能在邊緣輕掃才能滑動返回,這使用者體驗是不好的,接下來實現全屏滑動返回!
思路
既然自帶的滑動返回只能是在邊緣,那我們能不能修改使它觸控範圍變大甚至全屏呢?先來看下系統手勢有沒有提供屬性或方法供我們使用
NSLog(@"%@", self.interactivePopGestureRecognizer);
複製程式碼
列印資訊:
/*
<UIScreenEdgePanGestureRecognizer: 0x7fd542611e20; state = Possible;
delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7fd542706300>; target=
<(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition
0x7fd542611ce0>)>>
*/
複製程式碼
原來系統手勢的型別為 UIScreenEdgePanGestureRecognizer ,轉到定義,發現有一個屬性
UIRectEdge edges
複製程式碼
是個結構體
typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
UIRectEdgeNone = 0,
UIRectEdgeTop = 1 << 0,
UIRectEdgeLeft = 1 << 1,
UIRectEdgeBottom = 1 << 2,
UIRectEdgeRight = 1 << 3,
UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
}
複製程式碼
只提供了這幾樣,都是邊緣的,這樣就只好另尋他路了。
既然沒有提供方式給我們現實要求,那我們就自己新增一個拖動手勢 UIPanGestureRecognizer來替它執行滑動返回功能。
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];
[self.view addGestureRecognizer:pan];
複製程式碼
新增一個拖動手勢,讓他執行系統手勢的操作,呼叫handleNavigationTransition:方法(剛才列印的資訊中可以得知),現在的問題就是target是誰?
我們可以看看UIScreenEdgePanGestureRecognizer中是否有線索呢?
UIScreenEdgePanGestureRecognizer *gest = self.interactivePopGestureRecognizer;
複製程式碼
找了半天沒找著,但是UIScreenEdgePanGestureRecognizer繼承於UIPanGestureRecognizer,而UIPanGestureRecognizer又繼承於UIGestureRecognizer,在UIGestureRecognizer提供的方法中我們可以推斷出一定有target,而且還是強引用的私有屬性!那我們就可以用OC強大的殺手鐗KVC來得到這個屬性,但是前提是我們得知道target所指屬性是什麼名字
參照我的另一篇文章:iOS – 通過runtime獲取某個類中所有的變數和方法
// OC runtime 機制
// 只能動態獲取當前類的成員屬性,不能獲取其子類,或者父類的屬性
unsigned int count = 0;// 拷貝出所胡的成員變數列表
Ivar *ivars = class_copyIvarList([UIGestureRecognizer class], &count);
for (int i = 0; i<count; i++) {
// 取出成員變數
Ivar ivar = *(ivars + i);
// 列印成員變數名字
NSLog(@"%s", ivar_getName(ivar));
// 列印成員變數的資料型別
NSLog(@"%s", ivar_getTypeEncoding(ivar));
}
// 釋放
free(ivars);
複製程式碼
在列印中我們找到了UIGestureRecognizer的私有屬性 _targets,是個陣列,而且只有一個元素,元素的型別如圖所示
那就好辦了,這樣我們就可以得到target了
NSArray *targets = [gest valueForKeyPath:@"_targets"]; // 列印可以發現裡面就一個元素
id target = [targets[0] valueForKeyPath:@"_target"];
複製程式碼
這樣我們就差不多實現全屏滑動返回的功能,但是有個bug
如圖所示,在最後裡回到根控制器介面後我再一次向右滑動,接著點選Button,它沒有將FirstVC彈出,這就是傳說中的bug,那我們現在在做的,就是在根控制器不讓滑動返回生效,即禁用手勢。
監聽手勢,遵守協議UIGestureRecognizerDelegate,實現代理方法
// 噹噹前控制器是是根控制器時不讓移除當前控制器(換句話說就是禁止手勢)
pan.delegate = self;
複製程式碼
#pragma mark - UIGestureRecognizerDelegate// 當開始滑動時呼叫
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// 當為根控制器是不讓移除當前控制器,非根控制器時允許移除
NSLog(@"%ld", self.viewControllers.count);
BOOL open = self.viewControllers.count > 1;
return open;
}
複製程式碼
最後說兩句
這樣就可以全屏滑動了,不過讓我們來看看我們新增手勢的習慣
UIPanGestureRecognizer *myPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan)];
[self.view addGestureRecognizer:myPan];
myPan.delegate = self;
複製程式碼
我們在新增手勢時設定了target為self,而delegate也為self
那是不是可以推斷出系統手勢的delegate就是我們剛剛想要的target呢,答案是是的
id target = self.interactivePopGestureRecognizer.delegate;
複製程式碼
所以我們的target就可以通過這種方式獲得,不用KVC的方式
哦,最後別忘了禁用系統手勢
// 禁止系統的手勢
self.interactivePopGestureRecognizer.enabled = NO;
複製程式碼
這樣,我們就實現了全屏滑動返回的功能了~
原始碼
Objective-C
記得遵守協議: UIGestureRecognizerDelegate
LXFNavigationController.m
- (void)viewDidLoad {
[super viewDidLoad];
// 系統的手勢
UIScreenEdgePanGestureRecognizer *gest = self.interactivePopGestureRecognizer;
// target
id target = self.interactivePopGestureRecognizer.delegate;
// 禁止系統的手勢
self.interactivePopGestureRecognizer.enabled = NO;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];
[self.view addGestureRecognizer:pan];
// 監聽代理
pan.delegate = self;
}
複製程式碼
#pragma mark - UIGestureRecognizerDelegate
// 當開始滑動時呼叫
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// 當為根控制器是不讓移除當前控制器,非根控制器時允許移除
NSLog(@"%ld", self.viewControllers.count);
BOOL open = self.viewControllers.count > 1;
return open;
}
複製程式碼
Swift
LXFNavigationController.swift
override func viewDidLoad() {
super.viewDidLoad()
guard let targets = interactivePopGestureRecognizer!.value(forKey: "_targets") as? [NSObject] else { return }
let targetObjc = targets.first
let target = targetObjc?.value(forKey: "target")
let action = Selector(("handleNavigationTransition:"))
let panGes = UIPanGestureRecognizer(target: target, action: action)
view.addGestureRecognizer(panGes)
}
複製程式碼