美文网首页
iOS中事件的产生和传递

iOS中事件的产生和传递

作者: 6ffd6634d577 | 来源:发表于2016-05-13 10:50 被阅读47次
一.iOS中的事件可以分为3大类型
Snip20160513_1.png

响应者对象

  • 在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”

  • UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件

UIResponder

•UIResponder内部提供了以下方法来处理事件

触摸事件

-(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;

加速计事件

-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;

-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;

-(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

远程控制事件

-(void)remoteControlReceivedWithEvent:(UIEvent *)event;

UIView的触摸事件处理

•UIView是UIResponder的子类,可以实现下列4个方法处理不同的触摸事件

Ø一根或者多根手指开始触摸view,系统会自动调用view的下面方法
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

Ø一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

Ø一根或者多根手指离开view,系统会自动调用view的下面方法
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent  *)event


Ø触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

提示:touches中存放的都是UITouch对象

UITouch

•当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象

•一根手指对应一个UITouch对象

•UITouch的作用

Ø保存着跟手指相关的信息,比如触摸的位置、时间、阶段

•当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指的触摸位置。

•当手指离开屏幕时,系统会销毁相应的UITouch对象

'•提示:iPhone开发中,要避免使用双击事件!'

UITouch的属性

•触摸产生时所处的窗口
@property(nonatomic,readonly,retain)UIWindow    *window;

•触摸产生时所处的视图
@property(nonatomic,readonly,retain)UIView      *view;

•短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly)NSUInteger          tapCount;

•记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly)NSTimeInterval      timestamp;

•当前触摸事件所处的状态
@property(nonatomic,readonly)UITouchPhase        phase;

UITouch的方法

•-(CGPoint)locationInView:(UIView *)view;
Ø返回值表示触摸在view上的位置
Ø这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
Ø调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置

•-(CGPoint)previousLocationInView:(UIView *)view;
Ø该方法记录了前一个触摸点的位置

UIEvent

•每产生一个事件,就会产生一个UIEvent对象

•UIEvent:称为事件对象,记录事件产生的时刻和类型

•常见属性
Ø事件类型
@property(nonatomic,readonly)UIEventType     type;
@property(nonatomic,readonly)UIEventSubtype  subtype;

Ø事件产生的时间
@property(nonatomic,readonly)NSTimeInterval  timestamp;

•UIEvent还提供了相应的方法可以获得在某个view上面的触摸对像(UITouch)

touches和event参数

•一次完整的触摸过程,会经历3个状态:

Ø触摸开始:-(void)touchesBegan:(NSSet *)touches withEvent:(UIEven *)event

Ø触摸移动:-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

Ø触摸结束:-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

Ø触摸取消(可能会经历):-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event


•4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数
Ø一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数

Ø如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象

Ø如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象

Ø根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸

疑问:
默认触摸方法NSSet里面只能获得一个UITouch对象,为什么?

UIView默认不支持多点触控。也就是说不支持多只手指同时触摸。

如何让视图接收多点触摸?

需要设置它的multipleTouchEnabled属性为YES,默认状态下这个属性值为NO,即视图默认不接收多点触摸。。

如何判断用户当前是双击还是单击?

根据UITouch的tapCount属性的值。tapCount表示短时间内轻击屏幕的次数。因此可以根据tapCount判断单击、双击或更多的轻击。

根据tapCount点击的次数来设置当前视图的背景色(双击改变背景颜色)
轻击操作很容易引起歧义,比如当用户点了一次之后,并不知道用户是想单击还是只是双击的一部分,或者点了两次之后并不知道用户是想双击还是继续点击。为了解决这个问题,一般可以使用“延迟调用”函数,或手势识别器

一.使用“延迟调用”函数
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    if(touch.tapCount != 2){ // 如果不是双击
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(setBackgroundColor:)  object:[UIColor orangeColor]];
    } else { // 延时1执行改变背景的方法
        [self performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];
    }
}

二.使用Gesture Recognizer
使用Gesture Recognizer识别就会简单许多,只需添加两个手势识别器,分别检测单击和双击事件,设置必要的属性即可

- (id)init {  
    if ((self = [super init])) {  
    self.userInteractionEnabled = YES;  
        UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleSingleTap:)];  
        singleTapGesture.numberOfTapsRequired = 1;  
        singleTapGesture.numberOfTouchesRequired  = 1;  
        [self addGestureRecognizer:singleTapGesture];  
  
        UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleTap:)];  
        doubleTapGesture.numberOfTapsRequired = 2;  
        doubleTapGesture.numberOfTouchesRequired = 1;  
        [self addGestureRecognizer:doubleTapGesture];  
  
        [singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];  
    }  
    return self;  
}  
-(void)handleSingleTap:(UIGestureRecognizer *)sender{  
    CGPoint touchPoint = [sender locationInView:self];  
    //...  
}  
-(void)handleDoubleTap:(UIGestureRecognizer *)sender{  
    CGPoint touchPoint = [sender locationInView:self];  
    //...  
}

唯一需要注意的是
[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];  
这句话的意思时,只有当doubleTapGesture识别失败的时候(即识别出这不是双击操作),singleTapGesture才能开始识别,同我们一开始讲的是同一个问题。

提示:iPhone开发中,要避免使用双击事件!

NSObject类的cancelPreviousPerformRequestWithTarget:selector:object方法取消指定对象的方法调用。
官方对该方法解释:

Cancels perform requests previously registered with performSelector:withObject:afterDelay:. 
All perform requests are canceled that have the same target as aTarget, argument as anArgument, and selector as aSelector. 
如果是带参数,那取消时的参数也要一致,否则不能取消成功

细节
检测tapCount可以放在touchesBegan也可以touchesEnded,不过一般后者更准确,因为touchesEnded可以保证所有的手指都已经离开屏幕,这样就不会把轻击动作和按下拖动等动作混淆。

不管是一个手指还是多个手指,轻击操作都会使每个触摸对象的tapCount加1,因此可以直接调用touches的anyObject方法来获取任意一个触摸对象然后判断其tapCount的值即可。

二.事件的产生和传递

•发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中, 为什么是队列而不是栈?因为队列的特定是先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。

•UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)

应用如何找到最合适的控件来处理事件?
    • 1.首先判断主窗口(keyWindow)自己是否能接受触摸事件,不能,则传给UIApplication处理.,能,转2
    • 2.判断触摸点是否在自己身上
    • 3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
4.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。

•主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,但是这仅仅是整个事件处理过程的第一步

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

touchesBegan…
touchesMoved…
touchedEnded…

  • 注意: 如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件
UIView不接收触摸事件的三种情况
1.不接收用户交互
userInteractionEnabled = NO

2.隐藏
hidden = YES

3.透明
alpha = 0.0 ~ 0.01

提示:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的
事件传递示例
Snip20160513_2.png

•触摸事件的传递是从父控件传递到子控件

--点击了绿色的view:
UIApplication ->UIWindow->白色 ->绿色

--点击了蓝色的view:
UIApplication->UIWindow->白色 ->橙色 ->蓝色

--点击了黄色的view:
UIApplication->UIWindow->白色 ->橙色 ->蓝色 ->黄色


触摸事件处理的详细过程

•用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件

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

ØtouchesBegan…
ØtouchesMoved…
ØtouchedEnded…

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

响应者链条
  • 响应者链条:是由多个响应者对象连接起来的链条
  • 作用:能很清楚的看见每个响应者之间的联系,并且可以让一个事件多个对象处理。
  • 响应者对象:能处理事件的对象
事件传递的完整过程

1> 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。

2> 调用最合适控件的touches….方法

3> 如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者

4> 接着就会调用上一个响应者的touches….方法

如何判断上一个响应者

1> 如果当前这个view是控制器的view,那么控制器就是上一个响应者

2> 如果当前这个view不是控制器的view

  • 当前这个view的父类不是自定义的view,那么父控件就是上一个响应者
  • 当前这个view的父类是自定义的view,那么父类就是上一个响应者
响应者链的事件传递过程

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

hitTest:withEvent:方法和pointInside:withEvent:
    1. hitTest调用时机:当一个事件传递给一个控件的时候,系统就会调用这个方法
    2. hitTest作用: 寻找到最合适处理事件的view。
    * (回顾下事件传递),UIApplication -> UIWindow
    *  UIWindow去寻找最合适的view? [UIWindow hitTest:withEvent:]里面做了什么事情?
    1> 判断窗口能不能处理事件? 如果不能,意味着窗口不是最合适的view,而且也不会去寻找比自己更合适的view,直接返回nil,通知UIApplication,没有最合适的view。
    2> 判断点在不在窗口
    3> 遍历自己的子控件,寻找有没有比自己更合适的view
    4> 如果子控件不接收事件,意味着子控件没有找到最合适的view,然后返回nil,告诉窗口没有找到更合适的view,窗口就知道没有比自己更合适的view,就自己处理事件。

 * 验证下hitTest方法返回nil,里面的子控件能处理事件吗? 重写view的hitTest:withEvent:方法,
     * 验证这个方法是否真能找到最合适的view?
     * 如果点击屏幕任何一个地方,都由控制器的view来处理事件,怎么做? 直接返回白色的view,就不会继续去找白色view的子控件了。

• 2> hitTest:withEvent:方法的处理流程如下:
•    1、调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内
•        若返回NO,则hitTest:withEvent:返回nil;
•        若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有
•        子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图返
•        回非空对象或者全部子视图遍历完毕。
• 
•    2、若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束。
•    3、如所有子视图都返回nil,则hitTest:withEvent:方法返回自身(self)。

相关文章

  • iOS 事件响应链机制

    iOS中的事件的产生和传递 首先要知道 事件传递和响应过程 相反的。 事件的传递 当你点击了屏幕会产生一个触摸事件...

  • iOS中事件的产生和传递

    一.iOS中的事件可以分为3大类型 响应者对象 在iOS中不是任何对象都能处理事件,只有继承了UIResponde...

  • iOS中的事件的产生和传递

    一.触摸事件由触屏生成后如何传递到当前应用? 系统响应 1.手指触碰屏幕,屏幕感应到触碰后,将事件交由IOKit处...

  • iOS中的事件的产生和传递

    1.事件的产生 ●发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,为什么是队...

  • iOS 中事件的产生,传递和响应

    事件 在iOS中事件UIEvent主要分为以下3大类: 触摸事件: 手指触摸屏幕,点击,滑动等 加速计事件: 主要...

  • 事件传递和响应机制

    iOS中的事件 1.事件的生命周期 事件的生命周期从事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的...

  • iOS事件的传递和响应机制

    iOS中的事件 事件的生命周期 事件的生命周期从事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的vi...

  • 事件的产生和传递-iOS

    一、响应者对象 在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们...

  • iOS 事件的产生和传递

    事件的生命周期: 从父控件传递到子控件:UIApplication——>window——>寻找处理事件最合适的vi...

  • iOS触摸事件处理详解

    iOS中事件的产生和传递 1.发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的队列事件中...

网友评论

      本文标题:iOS中事件的产生和传递

      本文链接:https://www.haomeiwen.com/subject/zvhprttx.html