iOS---事件传递和响应机制

iOS 中的事件

  • 触摸事件
  • 加速计事件
  • 远程控制事件

响应者对象(UIResponder)

只有继承 UIResponder 的对象才能响应事件

  • UIApplication
  • UIViewControl
  • UIView
UIResponder内部提供了以下方法来处理事件触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;



touchesBegan 这个方法,如果是两个手指同时触摸,这个方法只会调用一次,方法中包含两个 UITouch 的对象 , 如果是一前一后的触摸,就会调用两次这个方法,方法中包含一个 UITouch 对象

注意:
1.想要处理 UIView 的触摸事件,就要继承 UIView ,然后重写 UIView 的触摸事件方法
2.UIViewControl 在其触摸事件方法中就可以处理

UITouch 对象

UITouch 的属性

触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow *window;

触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView *view
;

短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;

记录了触摸事件产生或变化时的时间,单位是秒@property(nonatomic,readonly) NSTimeInterval timestamp;

当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;


UITouch 的方法

(CGPoint)locationInView:(UIView *)view;
// 返回值表示触摸在view上的位置
// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
// 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置

(CGPoint)previousLocationInView:(UIView *)view;
// 该方法记录了前一个触摸点的位置


  • 当用户用一根手指触摸屏膜时,就会创建一个 UITouch 对象
  • 一根手指对应一个 UITouch 对象
  • 当手指移动时,系统会更新 UITouch 对象的位置等信息
  • 当手指离开时,相应的 UITouch 对象就会销毁


IOS 的事件产生和传递

事件产生

  1. 发生触摸事件后,系统会把事件加入到由 UIApplication 管理的事件队列
  2. UIApplication 会从事件队列中取出最前面的事件,并将事件分发下去处理,通常是先发给应用程序的KeyWindow
  3. KeyWindow 会找其视图层结构找到一个最适合的View 来处理事件


事件的传递

UIApplication -> Window -> 寻找最合适的View

如何寻找最合适的 View?

  1. 首先判断 KeyWind 是否能接受触摸事件
  2. 判断触摸点是否在窗口身上
  3. 从后往前遍历子控件,重复上述两个步骤
  4. 遍历到最合适的 View
  5. 如果没有最合适的 View ,那么就由 KeyWind 处理



⚠️注意点:

  1. 透明度 < 0.01 的控件,是不能接受触摸事件
  2. 如果父控件不能接收事件,那么子控件也不能
  3. 不允许交互:userInteractionEnabled = NO
  4. 不管控件能否接收触摸事件,主要有触摸,就会产生事件,只是这个事件会不会被处理

如何寻找最合适的 View 的底层揭秘

hitTest:withEvent:
pointInside

hitTest:withEvent:方法

  • 调用:主要有事件传递给一个控件,控件就会调用该方法
  • 作用:找到最合适的 View ,并返回该 View

拦截事件的处理

  • 正因为hitTest:withEvent:方法可以返回最合适的view,所以可以通过重写hitTest:withEvent:方法,返回指定的view作为最合适的view。
  • 不管点击哪里,最合适的view都是hitTest:withEvent:方法中返回的那个view。
  • 通过重写hitTest:withEvent:,就可以拦截事件的传递过程,想让谁处理事件谁就处理事件。


如果想指定 View 作为最适合的 View,有以下两种方法

  1. 在父控件的hitTest:withEvent:返回子控件
  2. 在子控件的hitTest:withEvent:返回自

⚠️注意点:
推荐方法一,不推荐方法二的原因是有可能不能成功返回子控件,如果触摸点不在子控件,而在另一个子控件,那么没办法返回该子控件。

事件传递的正真顺序

  1. 产生触摸事件
  2. UIApplication 事件队列
  3. [UIWindow hitTest:withEvent:];
  4. 返回更合适的 View
  5. [子控件 hitTest:withEvent:];
  6. 返回更合适的 View


#import "WSWindow.h"
@implementation WSWindow
// 什么时候调用:只要事件一传递给一个控件,那么这个控件就会调用自己的这个方法
// 作用:寻找并返回最合适的view
// UIApplication -> [UIWindow hitTest:withEvent:]寻找最合适的view告诉系统
// point:当前手指触摸的点
// point:是方法调用者坐标系上的点
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    // 1.判断下窗口能否接收事件
     if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha = 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
    //{
    // return NO;
    //}
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
    NSLog(@"%s",__func__);
    }
    @end


pointInside:withEvent:方法

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


事件响应

响应者链条:是由多个响应者连接起来的链条。

事件响应的流程

1.找到最合适的 View 的时候,就会调用自己 touchs 的方法处理事件。
2.touches默认做法是把事件顺着响应者链条向上抛.
2.1 首先看 initail View 能不能处理事件
2.2 不能就把事件传递给父控件,继续判断
2.3 没有一个控件能处理就抛给 Window
2.4 Window 处理不了,最后抛给 UIApplication
2.5 UIApplication 处理不了,把事件丢弃。
如果以上一个响应者重写了 touches 方法,就能处理事件

如何做到一个事件多个对象处理
因为系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到一个事件多个对象处理的目的。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
// 1.自己先处理事件...
NSLog(@"do somthing...");
// 2.再调用系统的默认做法,再把事件交给上一个响应者处理
[super touchesBegan:touches withEvent:event]; 
}


事件传递和事件响应的过程总结

  1. 事件产生,添加到 UIApplication 的事件队列
  2. UIApplication 把事件队列最前的事件传递给UIWindow
  3. UIWindow 调用 hitTest:withEvent: 返回一个合适的 View
  4. View 继续调用 hitTest:withEvent: 找到一个最适合的 View
  5. 事件传递给最适合的 View 就开始响应事件
  6. 如果最适合 View 不能处理事件,把事件抛给父控件
  7. 一直都没有控件可以处理事件,就把事件抛给 UIWindow
  8. UIWindow 处理不了就抛给 UIApplication
  9. 最后处理不了就把事件抛弃。

你可能感兴趣的:(iOS---事件传递和响应机制)