美文网首页
OC_ 事件传递机制和响应者链

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

作者: Init_ZSJ | 来源:发表于2017-03-17 11:30 被阅读0次

序言:翻阅资料,学习,探究,总结,借鉴,谢谢探路者,我只是个搬运工。
参考、转发资料:
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
    }

相关文章

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

    序言:翻阅资料,学习,探究,总结,借鉴,谢谢探路者,我只是个搬运工。参考、转发资料:http://www.jian...

  • iOS 响应链

    iOS开发 - 事件传递响应链iOS 响应者链,事件的传递事件传递之响应链Cocoa Touch事件处理流程--响...

  • iOS 事件传递机制

    事件传递机制 响应者链的事件传递过程:如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给...

  • iOS中事件处理机制——触摸、手势、控制

    响应者链 首先,想要理解事件的处理机制必须要知道iOS中响应者链,要明白事件是怎么传递的。 如上图,假设我们点击v...

  • 01进阶之路-UI视图

    1. 事件传递机制和响应者链条 学习链接 事件传递机制iOS中的事件可以分为3大类型 1 触摸事件 2 加速计...

  • iOS知识收集

    1. 响应者链 1.1 Cocoa Touch事件处理流程--响应者链 1.2 事件传递之响应链 多线程 http...

  • 外部较好的学习主题

    1、关于响应者链和事件传递https://segmentfault.com/a/11900000150606032...

  • 什么是响应者链?ios面试攻克篇(三)

    iOs中的响应者链( )是用于确定事件响应者的一种机制,其中的事件主要指触摸事件( ),该机制和UIKit中的UI...

  • iOS UI事件传递与响应者链

    iOS UI事件传递与响应者链 响应者链 响应者对象:继承自UIResponder的对象称之为响应者对象。UIAp...

  • iOS:事件传递,响应者链传递机制

    如何寻找最合适的view:事件传递 1.主窗口接收到应用程序传递过来的事件后,首先判断自己能否接手触摸事件。如果能...

网友评论

      本文标题:OC_ 事件传递机制和响应者链

      本文链接:https://www.haomeiwen.com/subject/lrfqnttx.html