OC_ 事件传递机制和响应者链

序言:翻阅资料,学习,探究,总结,借鉴,谢谢探路者,我只是个搬运工。
参考、转发资料:
http://www.jianshu.com/p/2e074db792ba
http://www.jianshu.com/p/2b34ea0b6762
http://blog.csdn.net/a316212802/article/details/50061317

1. 事件的传递、响应流程。

事件传递机制。首先要有个事件(这里我们我们只讲解最常用的触摸事件),然后怎么这个事件是被谁发现的存储在哪,中间是怎么个传递过程,怎么判断谁能执行谁不能执行,最后谁处理。我想这些就是我们需要理解掌握的。

  • 事件源:
    我们研究的是触摸事件,触摸事件其实也分为很多种。例如
    1. 轻击手势 TapGestureRecognizer
    2. 轻扫手势 SwipeGestureRecognizer
    3. 长按手势 LongPressGestureRecognizer
    4. 拖动手势 PanGestureRecognizer
    5. 捏合手势 PinchGestureRecognizer
    6. 旋转手势 RotationGestureRecognizer

为了方便以下讲述的使用轻击手势。

  • 事件是被谁发现的存储在哪
    当程序中发现触摸事件之后,系统会将事件加入到UIApplication管理的一个任务队列中,以堆的形式存储,先进先出先执行。

    • UIApplication是什么?
      UIApplication对象是应用程序的象征,每个应用都有一个自己的UIApplication对象。在一个iOS程序启动后创建的第一个对象就是UIApplication对象,所以在AppDelegate.m文件中执行了UIApplicationDelegate方法供我们使用,而且UIApplication对象以一个单例的形式创建的,所以在其他文件中都能通过单例的形式获取到UIApplication对象。
  • 中间是怎么个传递过程,怎么判断谁能执行谁不能执行,最后谁处理

  1. 系统会将事件加入到UIApplication管理的一个任务队列中,以栈的形式存储,先进先出先执行。
  2. UIApplication会将事件发送给我们最底层的窗口UIWindow,这里的UIWindow值的是keyWindow(主),也只有显示在keyWindow上的视图才能接受点击事件的响应。
  3. UIWindow将事件发送给控制器(或者视图),如果控制器能处理事件的响应,而且触摸点在自己的身上,那么继续寻找子视图。
  4. 遍历所有的子视图,重复上一步的判断,以此循环,直到条件不满足。
  5. 到了这里表示上一步骤的条件不满足。
- 如果找到的作用点在子视图上,但是子视图的userInteractionEnabled属性设置NO(是否响应的设置),那么这个事件就会被废弃。
- 如果找不到触摸点没有在点击的子视图上,那么这个事件由父视图执行。
- 如果摸点在子视图的区域,但是设置了隐藏或者透明度为0时,那这个事件同样由父视图执行。

这就是判断谁能执行谁不能执行,最后谁处理的理由。

2. 具体如何通过代码判断

  1. hitTest:withEvent:方法
  - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{ 
}

这个方法可以返回最合适的view,什么叫最合适,首先这个方法的返回值,可以是自己,也以是子视图,也或者是nil。在我们不重写这个方法的时候,就要子视图是否满足我们的查询要求。

  1. pointInside:withEvent:方法
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
  {
  }

方法判断触摸点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。

  1. 内部的实现大概是这样的
  - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
  // 1.判断下窗口能否接收事件
  if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil;
  // 2.判断下点在不在窗口上
  // 不在窗口上
  if ([self pointInside:point withEvent:event] == NO) return nil;
  // 3.从后往前遍历子控件数组
  int count = (int)self.subviews.count;
  for (int i = count - 1; i >= 0; i--)     {
      // 获取子控件
      UIView *childView = self.subviews[i];
      // 坐标系的转换,把窗口上的点转换为子控件上的点
      // 把自己控件上的点转换成子控件上的点
      CGPoint childP = [self convertPoint:point toView:childView];
      UIView *fitView = [childView hitTest:childP withEvent:event];
      if (fitView) {
          // 如果能找到最合适的view
          return fitView;
      }
  }
  // 4.没有找到更合适的view,也就是没有比自己更合适的view
  return self;
}
// 作用:判断下传入过来的点在不在方法调用者的坐标系上
// point:是方法调用者坐标系上的点
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
  // 判断点在不在区域内
   if (CGRectContainsPoint(self.button.bounds, tempoint))
  {
      return YES ;
    }
return NO ;
}

3. 响应者链

  • UIResponder作为一个响应者事件,所有的可交互控件都是UIResponder直接或者间接的子类。

  • 响应者链的起点为: 上面我们所寻找到的最最合适的View,而这个View将调用自身的
    touchesBegan:withEvent:方法来开始事件响应者链的起始传递。

  • 添加到屏幕中的视图层级关系,从最上层的可能是button的点击事件,传递给父类或者自身的控制器并调用父类的touchesBegan:withEvent:方法。一直到最底层显示的keyWindow上这就是响应者链的传递。响应者链是由多层视图组成的结构(不管是View,还是控制器都是继承于响应者类UIResponder的)。由最上面的子控件传递给最底层的window。

  • 如何判断上一个响应者

    • 如果当前这个view是控制器的view,那么控制器就是上一个响应者
    • 如果当前这个view不是控制器的view,那么父控件就是上一个响应者
  • 事件响应和响应者链的区别:

    • 事件响应是从最底层的keyWindow开始向上分发事件的,而响应者链是从最合适View开始向下传递的。 方向不同。

4. 实际案例。

  1. button部分视图区域超出了父视图,怎么实现点击超出部分也执行点击效果。
    例子:
// ViewController.m
    - (void)viewDidLoad {
    [super viewDidLoad];
      AView *aView = [[AView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)] ;
    aView.backgroundColor = [UIColor grayColor] ;
    [self.view addSubview:aView] ;
    
    [aView _initViews] ;
  }
  // AView.m
  // 创建试图
  - (void)_initViews
{
    self.button = [UIButton buttonWithType:UIButtonTypeCustom] ;
    self.button.frame = CGRectMake(-50, -50, 100, 100) ;
    self.button.backgroundColor = [UIColor redColor] ;
    [self.button addTarget:self action:@selector(buttoClick:) forControlEvents:UIControlEventTouchUpInside] ;
    [self addSubview:self.button] ;
}
// 点击方法
  - (void)buttoClick:(id)sender
{
    NSLog(@"点击了button") ;
}

如果在AView中不做任何处理,那么


分析图.png
  • 解决办法:
    在AView中重写hitTest:withEvent:方法
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    if (view == nil) {
/*
       // 将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值
       - (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;
     // 将像素point从view中转换到当前视图中,返回在当前视图中的像素值
       - (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;
     */
          CGPoint tempoint = [self.button convertPoint:point fromView:self];
        if (CGRectContainsPoint(self.button.bounds, tempoint))
        {
            view = self.button;
          }
      }
      return view;
}  
  ```
当点击的触摸手势在Button的视图上,这样手动判断返回我们指定的View为Button,就可以了。

2. 有时候我们经常有这样的需求,在子视图View中想拿到View所在的控制器进行一些操作,通过事件响应者链寻找子视图所在的控制器。(这里我们抛弃基础的通过superView的方法获取)
首先我们要明白一点就是:可交互控件都是UIResponder直接或者间接的子类。

import "UIView+ViewController.h"

@implementation UIView (ViewController)
/*
为UIView扩展一个类目,通过这个方法可以获取这个视图所在的控制器
*/

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

推荐阅读更多精彩内容