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

2017-03-17  本文已影响0人  Init_ZSJ

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

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

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

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

  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. 响应者链

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
    - (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扩展一个类目,通过这个方法可以获取这个视图所在的控制器
*/

使用方法,例如我们还是在我们刚写的deme中的button点击方法中使用,看看效果,记得导入类别。
上一篇 下一篇

猜你喜欢

热点阅读