导航控制器的左滑返回

2016年7月30日

导航控制器的左滑返回

默认状态下,系统提供了导航控制器的左滑返回的功能。系统功能的前提是:

  • 导航控制器back按钮是系统自带的---不能自定义leftBarButton
  • 系统只提供滑动边缘返回,不提供全屏滑动手势

接下来,我们来阐述如何已经自定义leftBarButton的前提下,实现滑动返回。

下面的方法,其实都是巧妙的借用了系统的返回方法。

1 左滑边缘返回

1.1 思考:

当我们自定义了leftBarButton之后,系统的左边缘滑动手势就不起作用了。我们怎么才能做到,自定义leftBarButton之后,还让系统的左边缘滑动手势起作用?

1.2 分析:

我们在自定义手势的时候,手势一旦添加,当触发手势的时候,就会按照action去执行响应的代码,系统提供的左滑动边缘返回的手势也是一样。对手势的监听(是否执行当前手势、执行的过程等)都是通过代理来完成的,也就是通过代理来控制当前手势触发后,是否执行相应代码。那我们就能得出一个结论:<b style="color:red">自定义leftBarButton后系统返回手势失效,根本原因是系统返回手势的代理在其中起作用,我们要做的是,禁止将系统返回手势的代理清空掉。这样手势就一直存在了。</b>

1.3 问题

滑动返回到上一个控制器,其根本原理是,将当前导航控制器的栈顶控制器出栈,pop掉。那么问题来了,当返回到根控制器后,如果再次尝试返回时,就意味着要讲根控制器pop掉,界面就会卡死。正常情况下,是由系统手势的代理去判断当前栈顶控制器是否为根控制器。所以我们的思路是:<b style="color:red">通过清空系统手势的代理来维持手势一致存在,确保左滑返回可用。且,当当前栈顶控制器为非根控制器的时候才清空系统手势的代理,如果是根控制器,就恢复之前的代理(意味着,清空之前要保存)</b>。

1.4 代码示例

CMNavigationController.m

@interface CMNavigationController () <UINavigationControllerDelegate>

@property(nonatomic,strong) id popGestureDelegate; //用来保存系统手势的代理

@end

@implementation CMNavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    #warning 我们不能直接将代理置空,需要根据当前栈顶控制器是否是根控制器进行判断。
    //    self.interactivePopGestureRecognizer.delegate = nil;

    #warning 第一步,先保存当前的代理
    self.popGestureDelegate = self.interactivePopGestureRecognizer.delegate;
    
    #warning 第二步,成为自己的代理,去监听pop的过程,pop之前判断是否为根控制器
    self.delegate = self;

}

#warning 第三步,监听pop的方法,判断当前的栈顶控制器是否为根控制器
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    //self.viewControllers[0]表示根控制器
    if([self.topViewController isEqual:self.viewControllers[0]])
    {
        //如果是根控制器,恢复系统手势默认的代理
        self.interactivePopGestureRecognizer.delegate = self.popGestureDelegate;
    
    }else
    {
        //如果是非根控制器,将系统手势的默认代理置空
        self.interactivePopGestureRecognizer.delegate = nil;
    }
}

2 左滑全屏(任意位置)返回

2.1 思考

就算我们不自定义leftBarButton,系统的手势也没办法帮我们实现全屏返回。<b style="color:red">也就是说,手势必须由我们自己去定义。</b>

2.2 分析

自定义手势的时候,我们只需要定义手势的执行方法action和方法的执行对象target即可。由于全屏返回这个手势功能比较复杂,<b style="color:red">所以我们目前的首要任务不是去自己实现action,而是去借用系统的target对象的action方法。</b>因为,不管是系统的左滑边缘返回手势,还是自定义全屏左滑返回手势,返回这个功能都是一样的。

2.3 问题

目前,我们的任务有两个:①找到系统的action方法名称;②找到系统的target对象(因为action方法是target对象的)。第一个任务很简单,通过打印系统的手势就可以知道方法名称。第二个任务就困难了。有两种方式:①runtime去分析当前系统的属性 ②通过自定义手势的规律去猜测。

2.4 找到action方法名称

第一步,打印系统手势

//1 打印系统手势
/**
 *  打印结果
    <UIScreenEdgePanGestureRecognizer: 0x7ffaea42b2f0; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7ffaea787b50>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7ffaea42ae20>)>>
 */
 
NSLog(@"\n%@ \n",self.interactivePopGestureRecognizer);

<b style="color:red">第二步,通过打印结果看,执行action方法是handleNavigationTransition </b>

2.5 找action方法的对象target
2.5.1 运行时runtime分析

第一步,进入系统手势UIScreenEdgePanGestureRecognizer的头文件,以及其父类,父类的父类的头文件。

/**
 *  进入头文件的目的是,确认头文件中关于target的说明,最后在UIGestureRecognizer这个超类中找到了关于initWithTarget的字眼,说明target很可能是这个类的私有属性。所以,我们可以通过KVC来获取其私有属性的值,这样就完成了找target的任务。问题来了,通过KVC来获取值,首先得知道属性的名称呀。所以,接下来我们要继续找到这个属性的名称
 */

第二步,找到target的属性

/**
 * 1 通过运行时函数,获取类的属性列表
   参数:__unsafe_unretained Class cls  表示要获取哪个类的属性列表
        unsigned int *outCount 表示属性列表数组的个数,传入的是指针,函数进行修改,我们就可以通过参数获得属性个数
   返回值:返回的是属性列表数组
 */
unsigned int outCount;
Ivar *ivars = class_copyIvarList([UIGestureRecognizer class], &outCount);


/**
 *  2 遍历,属性列表数组,并打印,观察其中的哪个属性与target有关
 
    结论:打印结果第一条,与其有关。 _targets
 */
for(int i=0;i<outCount;i++)
{
    //2.1 获取元素(元素就是该类的属性,是C语言的结构体)
    Ivar ivar = ivars[i];
    //2.2 获取元素的名称,通过C语言函数获取
    const char *ivar_name =  ivar_getName(ivar);
    //2.3 将C语言字符串包装成OC字符串,打印
    NSLog(@"%@",@(ivar_name));
    
}

/**
 *  3 根据第二步的打印结果,通过KVC将_targets的值取出来。并打印。
 * 
 *  结论,打印结果是一个数组,且只有一个元素(该元素是一个字典),将其取出来
 (
 "(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7f8e7b54c540>)"
 )

 */
id _targets = [self.interactivePopGestureRecognizer valueForKeyPath:@"_targets"];
NSLog(@"%@",_targets);


/**
 *  4 将上一步的数组第一个元素取出来,获取内部字典的target的属性值--这个属性值就是我们要找的target对象
 */
NSDictionary *dict = _targets[0];
id temp = [dict valueForKeyPath:@"target"];
NSLog(@"%@",temp);

第三步,自定义手势
到这里,我们已经找到了action=handleNavigationTransition: target=temp

//1 自定义手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:temp  action:@selector(handleNavigationTransition:)];
//2 添加手势
[self.view addGestureRecognizer:pan];
2.5.2 根据自定义手势的规律猜测

我们在自定义手势的时候,一般是这样的

UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self  action:@selector(panGetsture:)];

也就是说,执行手势的对象一般是self(而对于手势的监控室代理,所以是当前手势的代理)。我们可以推测,系统手势也是这样的,通过系统手势的代理来执行相对应的方法。<b style="color:red">所以tartget对象为:self.interactivePopGestureRecognizer.delegate</b>

2.6 问题

因为,目前手势有我们自己的自定义的,我们并没有判断当前是否是根控制器,也就是说当前手势在根控制器也会生效。当我们在根控制器滑动返回的时候,系统仍然会将当前控制器pop掉,但事实上,根控制器是不能被Pop掉的,不然会出现卡死的现象。所以,我们需要监控手势的状态,在即将执行手势之前,我们判断是否为根控制器,如果是,不执行手势。所以需要设置自定义手势的代理为自己。

pan.delegate = self;

//遵守协议UIGetstureRecognizerDelegate,实现代理方法
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    //在每次手势开始之前,调用这个方法

    if([self.topViewController isEqual:self.viewControllers[0]])
    {
        //如果当前栈顶控制器是根控制器,则返回NO,不执行手势
        return NO ;
    }
    else{
        //如果是非根控制器,执行首饰
        return  YES;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容