事件分类

对于 iOS 设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:

  • 触屏事件(Touch Event)
  • 运动事件(Motion Event)
  • 远端控制事件(Remote-Control Event)

触摸事件 - 响应者对象: UIResponder

只有继承了UIResponder的对象才能接受并处理对象: UIApplication, UIViewController, UIView

- (void)touchBegin:(NSSet*)touches withEvent:(UIEvent*)event;

- (void)touchMove:(NSSet*)touches withEvent:(UIEvent*)event; 

- (void)touchEnd:(NSSet*)touches withEvent:(UIEvent*)event; 

- (void)touchCancel:(NSSet*)touches withEvent:(UIEvent*)event;

UITouch

用户用一根手指触摸屏幕时,会创建UITouch对象+

self.transform = CGAffineTransformTranslate(self.transform, offsetX, 0);\/\/ 相对上一次
CGAffineTransformMake 相对最开始的位置
事件的产生(上到下)

点击屏幕会产生一个事件
系统会将改该事件加入到一个UIApplication管理的事件队列中。
UIApplication交给keywindow。
keywindow找到一个最合适的View来处理触摸事件。 事件由父类传给子类,找到合适的执行touch方法。
如果父控件不能接受事件,子类也不能接受事件

  • 三种情况不能接受事件:
    • userInteractionEnabled = NO;
    • hidden = YES;
    • alpha = 0.0 ~ 0.01 (UIImageView userInteractionEnable默认=NO)
    • child view's bound beyond its parent's view bounds, and parent's view clipsToBounds = NO, child view will show content beyond parent view's frame. Parent view will not response touch event in the place out of parent view's frame

Responder Chain 响应者链条 (下到上) - How to response event from first responder

FirstResponser --> VC orParent View--> TheWindow --> TheApplication --> nil(丢弃)

touchesBegain等方法是将事件顺着响应者链条向上传递,将事件交给上一个响应者处理
当前view是VC的view,那么控制器就是上一个响应者
当前view不是VC的view,那么父类就是上一个响应者

总结: 响应事件的传递过程

  • 如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图
  • 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
  • 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
  • 如果UIApplication也不能处理该事件或消息,则将其丢弃

Dispatch Event 事件分发 - How to find first responder

TheApplication --> TheWindow --> VC orParent View --> FirstResponser

  • 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: 方法返回此对象,处理结束;
  • 如所有子视图都返回空,则 hitTest:withEvent: 方法返回自身 (self)。

  • 如果最终 hit-test 没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果 UIWindow 实例和 UIApplication 实例都不能处理该事件,则该事件会被丢弃(这个过程即上面提到的响应值链);

  • hitTest:withEvent: 方法将会忽略

    • 隐藏 (hidden=YES) 的视图,
    • 禁止用户操作 (userInteractionEnabled=NO) 的视图,
    • 以及 alpha 级别小于 0.01(alpha<0.01)的视图。

    • 如果一个子视图的区域超过父视图的 bound 区域(父视图的 clipsToBounds 属性为 NO,这样超过父视图 bound 区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别, 因为父视图的 pointInside:withEvent: 方法会返回 NO, 这样就不会继续向下遍历子视图了。当然,也可以重写 pointInside:withEvent: 方法来处理这种情况。

  • 我们可以重写 hitTest:withEvent: 来达到某些特定的目的。 比如拦截子视图的事件

手势识别:UIGestureRecognizer

  • 用法:

  • 创建:

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
其他: tap.numberOfTapsRequired = 2;
添加到view上面 [_imageView addGestureRecogizer:tap];
_imageView.userInteractionEnabled = YES;

  • 手势代理:

results matching ""

    No results matching ""