UI控件无法响应点击等事件的探索

UI控件无法响应点击等事件的探索

一、响应者链

关于响应者链,有如下一段介绍:每一个应用有一个响应者链,我们的视图结构是一个N叉树(一个视图可以有多个子视图,一个子视图同一时刻只有一个父视图),而每一个继承UIResponder的对象都可以在这个N叉树中扮演一个节点。当叶节点成为最高响应者的时候,从这个叶节点开始往其父节点开始追朔出一条链,那么对于这一个叶节点来讲,这一条链就是当前的响应者链。响应者链将系统捕获到的UIEvent与UITouch从叶节点开始层层向下分发,期间可以选择停止分发,也可以选择继续向下分发。

那,我要是告诉你们,响应者链就是上面那段话介绍的,估计你们得拿板砖拍我了。这等于没说。别急,先来举个栗子:

我用SingleView模板创建了一个新的工程,它的主Window上只有一个UIViewController,其View之上有一个Button。这个项目中所有UIResponder的子类所构成的N叉树为这样的结构:

b26Nri.jpg!web

那么他看起来并不像N叉树,但是不代表者不是一颗N叉树,当我们项目复杂之后,这个View可不可以有多个UIButton节点?所以他就是一棵树。

实际上我们要把这棵树写完整,应该还要算上UIButton的UILabel和UIImageView,因为他们也是UIReponder的子类。这里先不考虑了。

所以我们尝试在任意地方打印这个Button的nextReponder对象。nextResponder对象是UIReponder类的实例方法,它会返回任意对象在树中的上一个响应者实例:

 
  1. NSLog(@"%@", self.btn.nextResponder);

控制台输出信息如下:

<UIView: 0x7f8973e92b60; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f8973e92ee0>>

咱们可以一直打印下去,获取下一个Responder的下一个Responder,依次获取的响应者是:

<ViewController: 0x7f8973c2ccf0>

(null)

为什么这里ViewController没有父亲呢?

注意这句代码我是写在ViewDidLoad中,而我们知道这个方法的生命周期比较早,所以我们换个地方写或者延迟一段时间再打印,两种方法都可以得到结果(由此可以推理出我们响应者树的构造过程是在ViewDidLoad周期中来完成的,这个函数会将当前实例的构成的响应者子树合并到我们整个根树中):

 
  1. double delayInSeconds = 2.0;
  2. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
  3. dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  4. NSLog(@"%@",self.btn.nextResponder.nextResponder.nextResponder);
  5. NSLog(@"%@",self.btn.nextResponder.nextResponder.nextResponder.nextResponder);
  6. NSLog(@"%@",self.btn.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder);
  7. NSLog(@"%@",self.btn.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder);
  8. });

最后获取就是上边给出的那幅图的样子了。那说了这么多,这个Responder到底有什么用呢?

在AppDelegate里面重写touchesBegan方法:

 
  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  2. {
  3. NSLog(@"AppDelegate接收到触摸事件");
  4. }

在ViewController里面也重写:

 
  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  2. {
  3. [super touchesBegan:touches withEvent:event];
  4. NSLog(@"ViewController接收到触摸事件");
  5. }

用户手指触摸到了UIView上,由于我们没有重写UIView的UITouchEvent,所以他里面和super执行的一样的,将该事件继续分发到UIViewController;

UIViewController的TouchBegan被我们重写了,如果我们不super,那么我们在这里写响应代码。事件到这里就不继续分发了。可想而知,UIViewController祖先节点:UIWindow,UIApplication,AppDelegate都无权被分发此事件。

如果我们super了TouchBegan,那么此次触摸事件由

ViewController分发给UIWindow,

UIWindow继而分发给UIApplication,

UIApplication再分发给AppDelegate,

于是我们在ViewController和appDelegate的touchBegan方法中都捕获到了这次事件。

但是这只是处理点击事件顺序,也即是确认了第一响应者之后的处理流程。

那寻找第一响应者的流程是怎样的呢。

其实就是逆向走一遍,当沿着这条响应者链找到了第一响应者,那么就会返回事件给自己的nextResponder去处理,直到appDelegate。

下面回到咱们的主题,既然点击按钮,没有触发点击事件,响应者链有很大嫌疑。

为什么这么说呢??

我问大家,如果你点击完按钮之后,没有找到第一响应者,或者是第一响应者找错了,还会调用button的触发事件吗??显而易见。

下面列举常见的几大原因,有兴趣的同学可以去试试。

  • 按钮不在响应者链上(比如我遇到的按钮从屏幕外推入屏幕内,不响应点击事件,初步猜想是button初始位置在UIWindow外,所以不在响应者链上)
  • 按钮的点击事件被其他控件拦截(比如按钮上面有按钮,点击上面的按钮)

二、视图生命周期

这个理解起来就简单一些,比如实现按钮点击事件所在的视图控制器被回收了。但是由于按钮被添加到当前可见视图上,按钮没有被回收,所以是可见的。但是点击这个按钮却没有任何作用。

三、总结

单单是上面两个方面并不能涵盖所有的类似情况,而且这节咱们只是从UIButton入手。在具体的开发中,有很多经验所不能够解释的现象,但是上面两点一般是优先考虑的。

转载于:https://my.oschina.net/fadoudou/blog/673376

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值