隨著手機(jī)屏幕的變大,原來右滑返回略顯不夠人性化,尤其是對手小的朋友,讓我如何單手玩手機(jī).對于app要全屏右滑或保持原生邊緣觸發(fā),各有說辭,這里不討論其好壞.
下面先看一下實(shí)現(xiàn)效果.

效果還不錯吧.當(dāng)然了,這里的所有效果都是系統(tǒng)實(shí)現(xiàn)的.或許你不信,一起看看實(shí)現(xiàn)吧.
#import "GLNavigationController.h"
@interface GLNavigationController () <UIGestureRecognizerDelegate>
@end
@implementation GLNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
// 這句很核心 稍后講解
id target = self.interactivePopGestureRecognizer.delegate;
// 這句很核心 稍后講解
SEL handler = NSSelectorFromString(@"handleNavigationTransition:");
// 獲取添加系統(tǒng)邊緣觸發(fā)手勢的View
UIView *targetView = self.interactivePopGestureRecognizer.view;
// 創(chuàng)建pan手勢 作用范圍是全屏
UIPanGestureRecognizer * fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler];
fullScreenGes.delegate = self;
[targetView addGestureRecognizer:fullScreenGes];
// 關(guān)閉邊緣觸發(fā)手勢 防止和原有邊緣手勢沖突
[self.interactivePopGestureRecognizer setEnabled:NO];
}
// 防止導(dǎo)航控制器只有一個rootViewcontroller時觸發(fā)手勢
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
//解決與左滑手勢沖突
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
if (translation.x <= 0) {
return NO;
}
// 過濾執(zhí)行過渡動畫時的手勢處理
if ([[self valueForKey:@"_isTransitioning"] boolValue]) {
return NO;
}
return self.childViewControllers.count == 1 ? NO : YES;
}
@end
在實(shí)現(xiàn)之前,先推測一下蘋果實(shí)現(xiàn)pop的大概思路.首先,需要在一個合適的view上添加邊緣手勢,其次,針對這個手勢必然要實(shí)現(xiàn)一個方法響應(yīng)該事件.當(dāng)然,根據(jù)蘋果一貫代碼風(fēng)格,處理該事件很可能交給另一個專門的類去處理.
假如以上推測成立,只要獲得那個專門處理事件的類和方法,實(shí)現(xiàn)全屏pop效果就很簡單了.
下面是筆者在分析蘋果實(shí)現(xiàn)pop的部分信息.看到這,是否若有所悟?
(lldb) pclass [self interactivePopGestureRecognizer]
// 信息->1
UIScreenEdgePanGestureRecognizer
| UIPanGestureRecognizer
| | UIGestureRecognizer
| | | NSObject
(lldb) pclass [self interactivePopGestureRecognizer].delegate
// 信息->2
_UINavigationInteractiveTransition
| _UINavigationInteractiveTransitionBase
| | UIPercentDrivenInteractiveTransition
| | | NSObject
(lldb) po [self interactivePopGestureRecognizer]
// 信息->3
<UIScreenEdgePanGestureRecognizer: 0x7fab1243be00; state = Possible; enabled = NO; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7fab126a4a60>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fab1243b850>)>>
(lldb) po [self interactivePopGestureRecognizer].delegate
// 信息->4
<_UINavigationInteractiveTransition: 0x7fab1243b850>
(lldb)
從信息1中,可以知道interactivePopGestureRecognizer屬性并不是UIGestureRecognizer類型的對象,而是其子類UIPanGestureRecognizer的子類UIScreenEdgePanGestureRecognizer類型的對象.
UIScreenEdgePanGestureRecognizer是邊緣觸發(fā)手勢,在系統(tǒng)中公有API,里面只有一個edges屬性,用來設(shè)置具體邊緣有效,如左邊緣.具體可以參考官方API.
在信息3中,可以看到
target= (action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fab1243b850>)
這樣一條信息,里面包含了target和action.看到這是不是很興奮?iOS開發(fā)者再也屬性不過的目標(biāo)-動作模式了.
到這里,已經(jīng)可以確定蘋果的實(shí)現(xiàn)方式是通過邊緣觸發(fā)手勢處理pop的.這里target是私有的,如何獲得呢?于是,網(wǎng)上很多人開始使用runtime來獲得一些私有的方法.筆者一般不愿在正式上線的項(xiàng)目中使用runtime獲得私有API,雖然不一定會被蘋果拒接,但是會有一定風(fēng)險,畢竟筆者最近人品還沒爆發(fā).
有沒不用運(yùn)行時的好方法?
先別著急,繼續(xù)看信息2和4, interactivePopGestureRecognizer的代理是_UINavigationInteractiveTransition,看類名可以想到該類和交互轉(zhuǎn)場相關(guān).分析到這里,基本上可以推測出蘋果是通過代理將事件處理委托給了_UINavigationInteractiveTransition對象.
在信息3中,可以看到target=<_UINavigationInteractiveTransition 0x7fab1243b850>的地址是0x7fab1243b850,信息4中<_UINavigationInteractiveTransition: 0x7fab1243b850>的地址也是0x7fab1243b850.
由以上分析,可以確定蘋果的實(shí)現(xiàn)方式是將處理邊緣觸發(fā)的事件的任務(wù)委托給了_UINavigationInteractiveTransition,在_UINavigationInteractiveTransition中有處理該事件的方法handleNavigationTransition:.
代碼分析
id target = self.interactivePopGestureRecognizer.delegate;
這句代碼目的是獲取事件處理對象.以便自己添加的手勢可以把事件處理委托給它.
SEL handler = NSSelectorFromString(@"handleNavigationTransition:");
這句就是獲取委托對象里的處理方法.
UIPanGestureRecognizer * fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler];
fullScreenGes.delegate = self;
[targetView addGestureRecognizer:fullScreenGes];
這幾句就是添加自己的全屏手勢,通過目標(biāo)-動作模式把任務(wù)交給了系統(tǒng)委托對象處理.
建議
如果需要自定制導(dǎo)航時,實(shí)現(xiàn)是寫在UINavigationController子類中,比較方便.如果不需要,可以單獨(dú)寫一個分類.這里寫在GLNavigationController中,其中GLNavigationController.h繼承自UINavigationController.
其他提示
- 如果你的導(dǎo)航在不同控制器間有隱藏狀態(tài)欄的話,隱藏方法需要使用帶有animated:參數(shù)的方法setNavigationBarHidden: animated:,否則過渡會出問題.
- 不用擔(dān)心審核問題,是可以通過的.
*溫馨提示:如果對筆者分析時使用的工具感興趣,可以在筆者的博客找到相關(guān)文章.