iOS的事件可以分为三类:触摸事件,加速计事件,远程控制事件
iOS中不是任何对象都能处理对象,只有继承了UIResponder的对象才能接收并处理事件. ---->响应者对象
我们可以观察到 UIView就是继承于UIResponder,所以所有可看到的控件都是可以接收到事件的.
UIApplication,UIViewController,UIView都继承自UIResponder,因此它们都是响应者对象, 都能够就收并处理事件
UIResponder内部提供了以下方法专门用来事件的处理
1.触摸事件:
--->我们经常用来测试用的方法:
- (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
触摸事件被打断
2.加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
加速计事件开始
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
加速计事件结束
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
加速计事件被打断
3.远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
接收远程控制事件
UIView的触摸事件处理
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
触摸事件开始[一根或者多根手指开始触摸view,系统会自动调用]
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
拖拽事件[在view上移动的时候,调用这方法]
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
触摸结束事件[手指离开view,调用这个方法]
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
触摸事件被打断[在触摸结束前,被系统事件打断,(如打电话,短信等等),系统自动调用]
注意:touches中存放的都是:UITouch对象.
UITouch的属性:
1.触摸产生时所处的窗口
@property(nonatomic,readonly,retain)UIWindow *window;
2.触摸产生时所处的视图
@property(nonatomic,readonly,retain)UIView *view;
3.短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击[一般不常用]
@property(nonatomic,readonly)NSUInteger tapCount;
4.记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly)NSTimeInterval timestamp;
5.当前触摸事件所处的状态
@property(nonatomic,readonly)UITouchPhase phase;
UITouch的方法
- (CGPoint)locationInView:(UIView *)view
1.返回值表示触摸在view的位置
2.如果传入的view为nil的话,返回的触摸点在UIWindow的位置
- (CGPoint)previousLocationInView:(UIView *)view
记录前一个触摸点的位置
代码实现简单的拖拽,来解释这个属性和方法
// 验证点击事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
}
// 验证拖拽移动事件
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
// 得到当前点按的位置,是针对与self(红色的view)来说的
CGPoint curP = [touch locationInView:self];
// 得到前一次点得位置
CGPoint lastP = [touch previousLocationInView:self];
// NSLog(@"%s",__func__);
// NSLog(@"%@",touch);
NSLog(@"%@-----%@",NSStringFromCGPoint(curP),NSStringFromCGPoint(lastP));
// 计算 x轴方向的偏移量(都是针对于上一次,也就是针对与上一次的改变)
CGFloat offsetX = curP.x - lastP.x;
// 计算 y轴的偏移量
CGFloat offsetY = curP.y - lastP.y;
// 在原有的基础上进行平移
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
// 点击结束后调用
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
}
// 程序被迫中断时调用,如电话打进了。
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
}
UIEvent
每产生一个事件,就会产生一个UIEvent对象, 内部记录着事件产生的时刻和类型
常见属性
1.事件类型
@property(nonatomic,readonly)UIEventType type;
@property(nonatomic,readonly)UIEventSubtype subtype;
2.事件产生的时间
@property(nonatomic,readonly)NSTimeInterval timestamp;
lUIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)
touches和event参数
一次完整的触摸过程,会经历3个状态:
1触摸开始:
- (void)touchesBegan:(NSSet *)touches withEvent (UIEvent *)event
2.触摸移动:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
3.触摸结束:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
4.触摸取消(可能会经历):
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数
5.一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数
5.1如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
5.2如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
5.3根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸
事件的产生和传递
1.发生触摸事件后,系统会将该事件加入一个UIApplication管理的事件队列中
2.UIApplication会从事件队列中取出最前面的事件,并将事件分发下去进行处理.通常先发送事件给应用程序的主窗口(keyWindow)
3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件., 这也是整个事件处理过程中的第一步,也是最重要的一部
4.找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
5.如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
那么如何找到最合适的控件来处理事件?
1.先查看自己是否能接收触摸事件
2.触摸点是否在自己身上
3.从后往前
遍历子控件,重复1,2步骤
4.如果没有符合条件的子控件,那么就是自己最适合处理
图形说明:
Snip20151007_3.png
触摸事件的传递;
1.点击了绿色的view
UIApplication-->UIWidow(keyWindow)-->白色的view-->绿色
2.点击了蓝色view
UIApplication-->UIWindow-->白色-->橙色-->蓝色
3.点击了黄色view
UIApplication-->UIWindow-->白色-->橙色-->蓝色-->黄色
UIView不接收触摸事件的三种情况:
1.不接收用户交互:
userInteractionEnabled = NO
2.隐藏
hidden = YES
3.透明度(近乎透明)
alpha = 0.0~0.01之间
提示: UIImageView的userInteractionEnabled默认为NO,因此UIImageView以及它的子控件默认是不接收触摸事件的
模拟苹果来完成它的底部实现过程,
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%@----%s",[self class],__func__);
}
// ponit是方法调用者坐标系上的触摸点位置--->当触摸事件产生时,会调用这个方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判断能否接收触摸事件 (继承与uirespond)(隐藏,交互,透明度)
if (self.hidden == YES || self.userInteractionEnabled == NO || self.alpha < 0.01 )
return nil;
// 触摸点的位置在不在控件上
if (![self pointInside:point withEvent:event]) return nil;
// 如果在控件上,则遍历控件的子控件(由后往前)
int count = (int)self.subviews.count;
for (int i = count - 1; i >= 0; i--) {
// 取出最前面的子控件
UIView *childView = self.subviews[i];
// 将触摸点的位置,转换成该控件的坐标系上的点
CGPoint childP = [self convertPoint:point toView:childView];
// 用点的位置,以及事件再次判断
UIView *fitView = [childView hitTest:childP withEvent:event];
// 如果找到合适的view,则返回合适的view
if (fitView) {
return fitView;
}
}
// 没有合适的view 则返回自己。
return self;
}
点击事件.gif
控制台的打印
Snip20151007_5.png
既然已经知道了,事件的传递过程,那么我们完全可能做出一些特殊的行为,去拦截操作, 一下两个方法都可以进行事件的拦截
// 判断触摸点在view上,是否能够接收事件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// 将点得坐标系改变
CGPoint blueP = [self convertPoint:point toView:_blueBtn];
// 判断是否在蓝色按钮上 ,
if ([self.blueBtn pointInside:blueP withEvent:event]) {
// 如果在,则不让它进行相应。
return NO;
}
// 判断继续(由系统自行判断)
return [super pointInside:point withEvent:event];
}
// 重写hittest 方法,可以拦截监听。 或者说,想让谁听,谁就听
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 将点得坐标系改变
CGPoint blueP = [self convertPoint:point toView:_blueBtn];
// 判断是否在蓝色按钮上 ,
if ([self.blueBtn pointInside:blueP withEvent:event]) {
return _blueBtn;
}
// 继续判断(由系统内部做决定)
return [super hitTest:point withEvent:
event];
//return self; 不能返回self,这样会造成,无论条件是否符合(能不能监听,点有没有在它上面),点都在yellow上
}
Snip20151007_6.png
点击拦截.gif
层级结构就是
黄色的view
在蓝色的按钮
上面按照,事件传递的规律,点击黄色区域应该是view做出响应,当然点击覆盖在按钮上的区域,也应该是view作出相应,但是由于我们将事件传递做出了改变,所以发生了事件拦截现象. 按钮能够监听点击事件
触摸事件处理的详细过程
1.用户点击了屏幕后产生一个触摸事件,经过一系列传递以后,会找到最合适的视图控件来处理这个事件
2.找到最合适的视图空间后,就会调用控件的touches方法来做具体事件处理
3.这些touches方法的默认做法是将事件顺着响应链条
向上传递,将事件交给上一个响应者进行处理
响应者链条示意图
1.响应者链条:是由多个响应者对象连接起来的链条
2.作用:能很清楚的看见每个响应者之间的对象,并且可以让一个事件多个对象处理
3.响应者对象:能处理事件的对象
Snip20151007_4.png
事件传递的完整过程
1.先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件
2.调用最合适控件的touches方法
3.如果调用了[super touches...];就会将事件顺着响应者链条往上传递,传递给上一个响应者
4.接着就会调用上一个响应者的touches...方法
如何判断上一个响应者
1.如果当前这个view是控制器的view,那么控制器就是上一个响应者
2.如果当前这个view不是控制器的view,那么父控件就是上一个响应者
响应者链的事件传递过程
1.如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图
2.在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
3.如果window对象也不处理,则其将事件或消息传递给UIApplication对象
4.如果UIApplication也不能处理该事件或消息,则将其丢弃
一个小应用来讲解事件的拦截
点击应用.gif// 监听按钮的点击
- (IBAction)PopClick:(PopButton *)sender {
UIButton *chatView = [UIButton buttonWithType:UIButtonTypeCustom];
[chatView setImage:[UIImage imageNamed:@"对话框"] forState:UIControlStateNormal];
[chatView setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
chatView.bounds = CGRectMake(0, -200, 200, 200);
// 它的父控件为基准
chatView.center = CGPointMake(100, -100);
// 将对话框加入父控件中 ,超出的部分仍然可以显示, 只是不能点击
[sender addSubview:chatView];
sender.chatView = chatView;
}
///自定义按钮,来实现变态拦截功能
// 外界提供_chatView用来接收新的对话框
// 重写hitTest方法,让能点击对话框。 只要拦截点击就行了
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 将点得坐标系转换
CGPoint chatP =[self convertPoint:point toView:_chatView];
// 判断点是否在 控件上
if ([_chatView pointInside:chatP withEvent:event]) {
return _chatView;
}
// 若不在,执行以前的方式
return [super hitTest:point withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint curOffset = [touch locationInView:self];
CGPoint lastOffset = [touch previousLocationInView:self];
CGFloat offsetX = curOffset.x -lastOffset.x;
CGFloat offsetY = curOffset.y - lastOffset.y;
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
网友评论