UIView触摸事件

UIView触摸事件

1.响应者对象(UIResponder)

只有继承UIResponder的对象才能处理触摸事件,比如:

  • UIApplication
  • UIViewController
  • UIView

UIResponder提供了以下方法处理触摸事件

1
2
3
4
- (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;

我们可以重写这些方法来处理触摸事件

2.UITouch对象

上面处理方法里的touches装的就是UITouch对象。单一根手指触摸屏幕开始,一个UITouch对象就被创建,保存着该触摸时间、位置、点击次数(连续点击间隔得小于一定值)等信息。直到手指离开屏幕(时间间隔大于连续点击时间间隔)UITouch对象才被销毁。 一个手指一个UITouch对象。

3.事件的产生与First Responder的查找

当手指触摸屏幕时,就产生一个触摸事件(UIEvent),接着系统就寻找一个最前面(在手指下面,离手指最近)的UIResponder(First Responder)来处理该事件。这个寻找的过程网上很多叫事件传递。
具体寻找过程是这样的:
首先,系统将该UIEvent加入UIApplication的事件队列,UIApplication从队列中取出,并调用UIWindow的hitTest:withEvent方法,该会返回可处理该事件的responder。
hitTest:withEvent逻辑如下:
判断 self.userInteractionEnabled && self.alpha>0.01 && !self.hidden && [self pointInside:point withEvent:event],为假则return nil,该responder及其subView就没机会处理该事件。否则,逆序遍历self.subviews,并调用subview的hitTest:withEvent,直到某个subview返回非nil对象,则返回该对象。没有subview返回非nil对象,则返回自己。
图解:
upload successful
upload successful
hitTest:withEvent可能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}

可见,一个view要接收UIEvent要满足四个条件:

  • 这个View不是隐藏的:

    1
    self.hidden == NO
  • 这个View允许用户交互:

    1
    self.userInteractionEnable == YES
  • 这个View透明度大于0.01:

    1
    self.alpha > 0.01
  • 这个View包含这个点:

    1
    [self pointInside:point withEvent:event] == YES

4.事件的响应

事件的响应就是调用上面说的UIResponder提供的方法如touchesBegan:withEvent:处理相应事件
在上面我们找到了First Reponder,这时就形成了一条从First responder 到window的响应链。

tips
查找First Responder是从window到Frist Responder,而响应链则从First Reponder到window,正好相反。

默认的touchesBegan:withEvent:只是把事件转发给响应链下一个responder。

注意:如果响应链中有view绑定了手势,那么这些手势会按响应链顺序得到该事件,进行匹配并处理,然后First Responder才得到对应事件,进行处理。