事件的产生和传递

iOS系统检测到手指触摸(Touch)操作时会将其放入到一个由UIApplication管理的事件队列中。UIApplication会从事件队列中取出最前面的触摸事件并传递给key window(当前接收用户事件的窗口)处理。 key window会调用hitTest:withEvent:方法在视图(UIView)层次结构中找到一个最合适的UIView来处理触摸事件。

hitTest:withEvent:方法的处理流程如下:

  1. 首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内。
  2. 若返回NO,说明触摸点不在当前视图内,则当前视图的hitTest:withEvent:返回nil
  3. 若返回YES,说明触摸点在当前视图内,则遍历当前视图的所有子视图(subviews),调用子视图的hitTest:withEvent:方法重复前面的步骤,所有子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕。
  4. 若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束。
  5. 如所有子视图都返回非,则hitTest:withEvent:方法返回自身self


找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理:

touchesBegan…
touchesMoved…
touchedEnded…
  • 这些touches方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理。

注意:如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件

如何找到最合适的控件来处理事件?

  • 自己是否能接收触摸事件?
  • 触摸点是否在自己身上?
  • 从后往前遍历子控件,重复前面的两个步骤
  • 如果没有符合条件的子控件,那么就自己最适合处理

事件传递的完整过程

  1. 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。
  2. 调用最合适控件的touches….方法。
  3. 如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者。
  4. 接着就会调用上一个响应者的touches….方法。

如何判断上一个响应者?

  1. 如果当前这个view是控制器的view,那么控制器就是上一个响应者。
  2. 如果当前这个view不是控制器的view,那么父控件就是上一个响应者。

响应者链的事件传递过程

对于左边的app,事件传递按下面的路径:

1.  Initial view 尝试着去处理事件或者消息。如果不能处理事件,它就递交事件给superview,因为这个initial view并不是视图控制器层级中得顶级view.
2.  这个superview尝试去处理该事件,如果superview不能处理该事件,它就递交事件给它的父view,因为它也不是view层级的顶级view。
3.  视图控制器的顶级view尝试着去处理该事件,如果连顶级view都不能处理该事件,它就递交事件给它的controller。
4.  这个viewcontroller尝试着去处理该事件,并且如果它不能处理该事件,它就会递交事件给window。
5.  如果window不能处理该事件,它就递交事件给singlegon app object(既UIApplication)
6.  如果连application都不能处理该事件,那么毫无疑问该事件将会被丢弃。

右边的应用传递按照稍微不同的路径,但是所有的事件传递都遵循相同的传递规则:

1.  一个view controller层级中得view向上递交事件知道它到达顶级view。
2.  顶级view递交事件给它的controller。
3.  Viewcontroller递交事件给它的顶级view的superview,步骤1-3重复直到它到达rootcontroller。
4.  这个rootviewcontroller递交事件给window对象。
5.  window对象递交事件给application对象。


UIView不接收触摸事件的三种情况

hitTest:withEvent:方法忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。

提示:UIImageViewuserInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的。