事件
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 按照逆顺序遍历
- 在代码中是嵌套调用
如上图,当我们点击 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 事件
网友评论