2016年7月30日
導航控制器的左滑返回
默認狀態(tài)下魂贬,系統(tǒng)提供了導航控制器的左滑返回的功能。系統(tǒng)功能的前提是:
- 導航控制器back按鈕是系統(tǒng)自帶的---不能自定義leftBarButton
- 系統(tǒng)只提供滑動邊緣返回,不提供全屏滑動手勢
接下來,我們來闡述如何已經(jīng)自定義leftBarButton的前提下,實現(xiàn)滑動返回捎拯。
下面的方法,其實都是巧妙的借用了系統(tǒng)的返回方法盲厌。
1 左滑邊緣返回
1.1 思考:
當我們自定義了leftBarButton之后署照,系統(tǒng)的左邊緣滑動手勢就不起作用了。我們怎么才能做到吗浩,自定義leftBarButton之后建芙,還讓系統(tǒng)的左邊緣滑動手勢起作用?
1.2 分析:
我們在自定義手勢的時候懂扼,手勢一旦添加禁荸,當觸發(fā)手勢的時候,就會按照action去執(zhí)行響應的代碼阀湿,系統(tǒng)提供的左滑動邊緣返回的手勢也是一樣赶熟。對手勢的監(jiān)聽(是否執(zhí)行當前手勢、執(zhí)行的過程等)都是通過代理來完成的陷嘴,也就是通過代理來控制當前手勢觸發(fā)后映砖,是否執(zhí)行相應代碼。那我們就能得出一個結(jié)論:<b style="color:red">自定義leftBarButton后系統(tǒng)返回手勢失效灾挨,根本原因是系統(tǒng)返回手勢的代理在其中起作用邑退,我們要做的是,禁止將系統(tǒng)返回手勢的代理清空掉劳澄。這樣手勢就一直存在了地技。</b>
1.3 問題
滑動返回到上一個控制器,其根本原理是秒拔,將當前導航控制器的棧頂控制器出棧莫矗,pop掉。那么問題來了砂缩,當返回到根控制器后作谚,如果再次嘗試返回時,就意味著要講根控制器pop掉梯轻,界面就會卡死食磕。正常情況下,是由系統(tǒng)手勢的代理去判斷當前棧頂控制器是否為根控制器喳挑。所以我們的思路是:<b style="color:red">通過清空系統(tǒng)手勢的代理來維持手勢一致存在,確保左滑返回可用。且伊诵,當當前棧頂控制器為非根控制器的時候才清空系統(tǒng)手勢的代理单绑,如果是根控制器,就恢復之前的代理(意味著曹宴,清空之前要保存)</b>搂橙。
1.4 代碼示例
CMNavigationController.m
@interface CMNavigationController () <UINavigationControllerDelegate>
@property(nonatomic,strong) id popGestureDelegate; //用來保存系統(tǒng)手勢的代理
@end
@implementation CMNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
#warning 我們不能直接將代理置空,需要根據(jù)當前棧頂控制器是否是根控制器進行判斷笛坦。
// self.interactivePopGestureRecognizer.delegate = nil;
#warning 第一步区转,先保存當前的代理
self.popGestureDelegate = self.interactivePopGestureRecognizer.delegate;
#warning 第二步,成為自己的代理版扩,去監(jiān)聽pop的過程废离,pop之前判斷是否為根控制器
self.delegate = self;
}
#warning 第三步,監(jiān)聽pop的方法礁芦,判斷當前的棧頂控制器是否為根控制器
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//self.viewControllers[0]表示根控制器
if([self.topViewController isEqual:self.viewControllers[0]])
{
//如果是根控制器蜻韭,恢復系統(tǒng)手勢默認的代理
self.interactivePopGestureRecognizer.delegate = self.popGestureDelegate;
}else
{
//如果是非根控制器,將系統(tǒng)手勢的默認代理置空
self.interactivePopGestureRecognizer.delegate = nil;
}
}
2 左滑全屏(任意位置)返回
2.1 思考
就算我們不自定義leftBarButton柿扣,系統(tǒng)的手勢也沒辦法幫我們實現(xiàn)全屏返回肖方。<b style="color:red">也就是說,手勢必須由我們自己去定義未状。</b>
2.2 分析
自定義手勢的時候俯画,我們只需要定義手勢的執(zhí)行方法action和方法的執(zhí)行對象target即可。由于全屏返回這個手勢功能比較復雜司草,<b style="color:red">所以我們目前的首要任務不是去自己實現(xiàn)action活翩,而是去借用系統(tǒng)的target對象的action方法。</b>因為翻伺,不管是系統(tǒng)的左滑邊緣返回手勢材泄,還是自定義全屏左滑返回手勢,返回這個功能都是一樣的吨岭。
2.3 問題
目前拉宗,我們的任務有兩個:①找到系統(tǒng)的action方法名稱;②找到系統(tǒng)的target對象(因為action方法是target對象的)辣辫。第一個任務很簡單旦事,通過打印系統(tǒng)的手勢就可以知道方法名稱。第二個任務就困難了急灭。有兩種方式:①runtime去分析當前系統(tǒng)的屬性 ②通過自定義手勢的規(guī)律去猜測姐浮。
2.4 找到action方法名稱
第一步,打印系統(tǒng)手勢
//1 打印系統(tǒng)手勢
/**
* 打印結(jié)果
<UIScreenEdgePanGestureRecognizer: 0x7ffaea42b2f0; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7ffaea787b50>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7ffaea42ae20>)>>
*/
NSLog(@"\n%@ \n",self.interactivePopGestureRecognizer);
<b style="color:red">第二步葬馋,通過打印結(jié)果看卖鲤,執(zhí)行action方法是handleNavigationTransition </b>
2.5 找action方法的對象target
2.5.1 運行時runtime分析
第一步肾扰,進入系統(tǒng)手勢UIScreenEdgePanGestureRecognizer的頭文件,以及其父類蛋逾,父類的父類的頭文件集晚。
/**
* 進入頭文件的目的是,確認頭文件中關于target的說明区匣,最后在UIGestureRecognizer這個超類中找到了關于initWithTarget的字眼偷拔,說明target很可能是這個類的私有屬性。所以亏钩,我們可以通過KVC來獲取其私有屬性的值莲绰,這樣就完成了找target的任務。問題來了姑丑,通過KVC來獲取值蛤签,首先得知道屬性的名稱呀。所以彻坛,接下來我們要繼續(xù)找到這個屬性的名稱
*/
第二步顷啼,找到target的屬性
/**
* 1 通過運行時函數(shù),獲取類的屬性列表
參數(shù):__unsafe_unretained Class cls 表示要獲取哪個類的屬性列表
unsigned int *outCount 表示屬性列表數(shù)組的個數(shù),傳入的是指針昌屉,函數(shù)進行修改钙蒙,我們就可以通過參數(shù)獲得屬性個數(shù)
返回值:返回的是屬性列表數(shù)組
*/
unsigned int outCount;
Ivar *ivars = class_copyIvarList([UIGestureRecognizer class], &outCount);
/**
* 2 遍歷,屬性列表數(shù)組间驮,并打印躬厌,觀察其中的哪個屬性與target有關
結(jié)論:打印結(jié)果第一條,與其有關竞帽。 _targets
*/
for(int i=0;i<outCount;i++)
{
//2.1 獲取元素(元素就是該類的屬性扛施,是C語言的結(jié)構(gòu)體)
Ivar ivar = ivars[i];
//2.2 獲取元素的名稱,通過C語言函數(shù)獲取
const char *ivar_name = ivar_getName(ivar);
//2.3 將C語言字符串包裝成OC字符串屹篓,打印
NSLog(@"%@",@(ivar_name));
}
/**
* 3 根據(jù)第二步的打印結(jié)果疙渣,通過KVC將_targets的值取出來。并打印堆巧。
*
* 結(jié)論妄荔,打印結(jié)果是一個數(shù)組,且只有一個元素(該元素是一個字典)谍肤,將其取出來
(
"(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7f8e7b54c540>)"
)
*/
id _targets = [self.interactivePopGestureRecognizer valueForKeyPath:@"_targets"];
NSLog(@"%@",_targets);
/**
* 4 將上一步的數(shù)組第一個元素取出來啦租,獲取內(nèi)部字典的target的屬性值--這個屬性值就是我們要找的target對象
*/
NSDictionary *dict = _targets[0];
id temp = [dict valueForKeyPath:@"target"];
NSLog(@"%@",temp);
第三步,自定義手勢
到這里荒揣,我們已經(jīng)找到了action=handleNavigationTransition: target=temp
//1 自定義手勢
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:temp action:@selector(handleNavigationTransition:)];
//2 添加手勢
[self.view addGestureRecognizer:pan];
2.5.2 根據(jù)自定義手勢的規(guī)律猜測
我們在自定義手勢的時候篷角,一般是這樣的
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGetsture:)];
也就是說,執(zhí)行手勢的對象一般是self(而對于手勢的監(jiān)控室代理系任,所以是當前手勢的代理)恳蹲。我們可以推測虐块,系統(tǒng)手勢也是這樣的,通過系統(tǒng)手勢的代理來執(zhí)行相對應的方法阱缓。<b style="color:red">所以tartget對象為:self.interactivePopGestureRecognizer.delegate</b>
2.6 問題
因為非凌,目前手勢有我們自己的自定義的举农,我們并沒有判斷當前是否是根控制器荆针,也就是說當前手勢在根控制器也會生效。當我們在根控制器滑動返回的時候颁糟,系統(tǒng)仍然會將當前控制器pop掉航背,但事實上,根控制器是不能被Pop掉的棱貌,不然會出現(xiàn)卡死的現(xiàn)象玖媚。所以,我們需要監(jiān)控手勢的狀態(tài)婚脱,在即將執(zhí)行手勢之前今魔,我們判斷是否為根控制器,如果是障贸,不執(zhí)行手勢错森。所以需要設置自定義手勢的代理為自己。
pan.delegate = self;
//遵守協(xié)議UIGetstureRecognizerDelegate篮洁,實現(xiàn)代理方法
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
//在每次手勢開始之前涩维,調(diào)用這個方法
if([self.topViewController isEqual:self.viewControllers[0]])
{
//如果當前棧頂控制器是根控制器,則返回NO袁波,不執(zhí)行手勢
return NO ;
}
else{
//如果是非根控制器瓦阐,執(zhí)行首飾
return YES;
}
}