一.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)。
网友评论