您的位置:首页 > 移动开发 > IOS开发

ios-提升之【4】-事件传递

2016-02-21 22:04 465 查看
原文:http://www.jianshu.com/p/82024d85c529

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也不能处理该事件或消息,则将其丢弃
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: