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

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

1、响应者链

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

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

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

b26Nri.jpg!web

那么他看起来并不像N叉树,可是不表明者不是一颗N叉树,当咱们项目复杂以后,这个View可不能够有多个UIButton节点?因此他就是一棵树。函数

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

因此咱们尝试在任意地方打印这个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>ci

(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外,因此不在响应者链上)
  • 按钮的点击事件被其余控件拦截(好比按钮上面有按钮,点击上面的按钮)

2、视图生命周期

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

3、总结

单单是上面两个方面并不能涵盖全部的相似状况,并且这节我们只是从UIButton入手。在具体的开发中,有不少经验所不可以解释的现象,可是上面两点通常是优先考虑的。

相关文章
相关标签/搜索