一.事件分类
对于 iOS 设备用户来说,操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:
- 触屏事件(Touch Event)如视图的单击、长按、滑动等等。
- 运动事件(Motion Event)如手机晃动。
- 远端控制事件(Remote-Control Event)如耳机控制按钮。
二.事件传递分发和响应
在触发一个事件后,会进行两步处理过程:事件传递和事件响应。它们分别上一个向下和向上的过程,这两个过程共同组成一个事件的处理循环。
事件传递
iOS 系统检测到手指触摸 (Touch) 操作时会将其打包成一个 UIEvent 对象,并放入当前活动 Application 的事件队列,UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,UIWindow 对象首先会使用hitTest:withEvent:
方法寻找此次 Touch 操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。
hitTest:withEvent:
方法的处理流程如下:
- 首先调用当前视图的
pointInside:withEvent:
方法判断触摸点是否在当前视图内。 - 若返回 NO, 则
hitTest:withEvent:
返回 nil,若返回 YES, 则向当前视图的所有子视图 (subviews) 发送hitTest:withEvent:
消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从 subviews 数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕。 - 若第一次有子视图返回非空对象,则
hitTest:withEvent:
方法返回此对象(找到First Responder),处理结束。 - 如所有子视图都返回空,则 hitTest:withEvent: 方法返回自身 (self)。
当整个事件传递完成后就构成了一条事件的响应链。
利用hitTest-View来实现点击区域的自定义:
我们可以通过判断当前触摸点是否在我们响应的区域上来实现对部分区域事件的不响应(截取)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* hitView = [super hitTest:point withEvent:event];
if (hitView == self)
{
for (int i = 0; i < _scrollModeTitle.count; i++)
{
UILabel *page = [_scrollView viewWithTag:i];
CGPoint pointPage = [self convertPoint:point toView:page];
if (CGRectContainsPoint(page.bounds, pointPage)) {
return page;
}
}
return self.scrollView;
}
else
return hitView;
}
事件响应
所有事件响应的类都是 UIResponder 的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。
First Responser --> The Window --> The Application --> nil(丢弃)
根据事件类型的不同响应事件分别调用不同的响应方法:
触摸方法:touchesBegan,touchesMoved,touchesEnded。
运动方法:motionBegan,motionEnded,motionCancelled。
远程方法:remoteControlReceivedWithEvent。