iOS 事件机制

事件

iOS 将事件分为三类:

  • Touch
  • Motion
  • Remote
    像耳机线控……

Touch 事件

Touch 事件的过程:事件产生 ==》 事件分发 ==》 事件响应

事件产生

iOS每产生一个事件都会生成一个 UIEvent 对象,它记录了事件的类型( UIEventType / UIEventSubtype (主要用在Motion和Remote) )、时间、几个手指触控等信息
当手指触摸屏幕时,每个手指都会产生一个 UITouch 对象,它保存着跟手指相关的信息,如触摸的位置、时间、阶段( UITouchPhase )等
UIEvent 和 UITouch 关系 -- UIEvent 有一方法 - allTouches ,返回 NSSet 集合的一组 UITouch 对象,即一个 UIEvent 包含一个或多个 UITouch 对象

确定点击对象

Hit Test -- iOS 通过 Hit Test 来寻找触摸点下面的 view 是什么

[UIView class]
- hitTest:withEvent:

Hit Test小结:

  • 从 UIWindow 开始,先父 view 后子 view
  • subViews 按照逆顺序遍历
  • 在代码中是嵌套调用

iOS 事件机制_第1张图片

如上图,当我们点击 view4 的区域,有

hit test from view TestWindow
hit test from view 0
hit test from view 2
return hit view (null), self view 2
hit test from view 1
hit test from view 4
return hit view 4, self view 4
return hit view 4, self view 1
return hit view 4, self view 0
return hit view 4, self view TestWindow

事件分发

UIApplication 和 UIWindow 有方法 - sendEvent: ,用于把事件分发到 hitTest View

UIApplication == sendEvent: ==> UIWindow == sendEvent: ==> hitTest View

事件响应

能响应事件的类必须继承于一个类 -- UIResponder,UIApplication / UIViewController / UIView( UI ) / AppDelegate 都继承于 UIResponder
通常来说,我们首先找到的 hitTest View,就是 Touch 事件的第一个 Responder

UIResponder 的响应过程

触摸开始 ==》 触摸移动 ==》 触摸结束 ,还有触摸取消

- touchesBegan:withEvent:
- touchesMoved:withEvent:
- touchesEnded:withEvent:
- touchesCancelled:withEvent:
UIResponder 的响应顺序

响应链 ( Responder Chain ) 即一系列关联的响应对象 ( a series of linked responder objects )
first responder ==> next responder ==> ... ==> UIWindow ==> UIApplication ==> AppDelegate ==> 丢弃
subView/hitTestView ==> superView/VC.view ==> VC ==> VC' superView ==> root VC ==> window ==> application ==> AppDelegate ==> 丢弃

  • 如果想事件继续传递下去,可以调用 [super touchesBegan:touches withEvent:event],不建议直接调 [self.nextResponder touchesBegan:touches withEvent:event]
UIView 不响应事件的条件
  • userInteractionEnabled = NO
  • hidden = YES
  • alpha ( 0-0.01 )

UIView 加大点击区域

假设一个按钮,希望能在其显示区域的一定范围外,也能响应点击事件

  • 最直观的做法:加大透明按钮
  • 事件机制 hitTest
    在父 view 的 - hitTest:withEvent: 方法中,判断如果点击的point 在要求的 rect 中时,直接返回 button ,否则返回 [super hitTest:point withEvent:event] 继续传递
    • pointInside:withEvent:
  • hitTest:withEvent: 方法会递归地调用 - pointInside:withEvent: 方法,- pointInside:withEvent: 用来判断触摸的 point 是否在 view 的范围内
    button 可以重写这一方法
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat buttonExtraPadding = 20;

    CGPoint convertPoint = [self convertPoint:point toView:self.superview];
    CGRect targetRect = CGRectInset(self.frame, - buttonExtraPadding, - buttonExtraPadding);
    if (CGRectContainsPoint(targetRect, convertPoint))
    {
        return YES;
    }

    return [super pointInside:point withEvent:event];  
}

子 view 超过父 view 范围

当子 view 超出父 view 的范围时,在父 view 范围内的部分能够响应事件,超出父 view 部分则不能响应事件
因为在 hitTest View 时,父 view 的 hitTest View 监测到不在范围内,因而也不会递归调用到子 view 的 hitTest

// 重写父 view 的 - pointInside:withEvent: 方法,使当确定 point 在子 view 范围内时,返回 YES
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // point 需要转换到子 view 的坐标系
    if ([_button pointInside:[self convertPoint:point toView:_button] withEvent:event])
    {
        return YES;
    }

    return [super pointInside:point withEvent:event];
}

全局监控 touch 事件

  • UIWindow 子类
    UIWindow 有 - sendEvent: 方法,可以捕获到所有的 Touch 事件

你可能感兴趣的:(iOS 事件机制)