不要怂一起看看响应者链和事件传递
在iOS中,只有继承了UIResponder的对象才可以接受并处理时间,这些对象被称为响应者对象
> UIApplication
> UIViewController
> UIView
这些都继承自UIResponder,因此他们都是响应者对象,都能够接收并且处理事件,UIResponder提供了以下方法来处理事件
触摸事件
// 开始点击
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
// 当手指在view上移动的时候
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
// 当手指离开这个view的时候
- (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;
------- 当要支持多根手指点击设置属性 : mutableTouch --------------
事件对象:UIEvent
UIEvent表示用户交互的事件对象,有以下属性
@property(nonatomic,readonly) UIEventType type;
@property(nonatomic,readonly) UIEventSubtype subtype;
其中type表示当前响应事件的类型,分别有多点触控、摇一摇、远程操控、3DTouch,在一个用户点击事件处理的过程中,UIEvent对象时唯一的
点击对象:UITouch
UITouch表示单个点击,其类文件中存在枚举类型UITouchPhase的属性,用来表示当前点击的状态。这些状态包括点击开始、移动、停止不动、结束、取消五个状态,每次点击发生的时候,点击对象都放在一个集合中传入UIResponder的回调方法中,我们通过集合中对象获取用户点击的位置
============属性===============
触摸产生时所处的窗口
@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;
============方法===============
// 返回值表示触摸在view上的位置
// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
// 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
-(CGPoint)locationInView:(UIView *)view;
// 该方法记录了前一个触摸点的位置
-(CGPoint)previousLocationInView:(UIView *)view;
-(CGPoint)locationInView:(UIView *)view 获取当前点击坐标点
-(CGPoint) previousLocationInView: (UIview *) view 获取上个点击位置的坐标点
除了touch回调的几个点击事件,手势UIGestureRecognizer对象也附加在view上,来实现其他手势事件,在view添加单击手势之后,原来的touchesEnded就无效了
UITouch的作用,保存着跟手指相关的信息,比如位置、时间、阶段,当手指离开时,系统会销毁响应的UITouch对象。
事件的传递和原理
1 > 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中(为什么是队列而不是栈呢?因为队列是FIFO,也就是先进先出,先产生的事件先处理才对,栈是先进后出的。),UIApplication会从事件中取出最前面的事件,发送给程序的主窗口--->keyWindow,主窗口会在视图层次结构中找到一个最适合的试图来处理事件。
2 > 找到最合适的试图空间后,会调用试图控件的触摸事件的方法
示例:
点击了绿色:UIApplication --> UIWindow --> 白色 ---> 绿色
点击了蓝色:UIApplication --> UIWindow --> 白色 ---> 橙色 ---> 蓝色
如果父控件不能接受触摸事件,那么子控件就不可能接受到触摸事件
一个试图无法接收触摸事件可能是以下情况
1、不接收用户交互
userInteractionEnabled = NO
2、隐藏
hidden = yes;
3、透明度
alpha = 0;
其中:UIImageView的userInteractionEnable默认为NO,因此UIImageVIew以及它的子控件默认不能接受触摸事件,如果想让某个view不能处理事件,那么就设置这三个属性
那么怎么找到最合适的控件来处理事件呢?
1️⃣、首先判断主窗口是否能接受事件
2️⃣、判断触摸点是否在自己身上
3️⃣、子控件数组中从后往前遍历子控件,重复1️⃣、2️⃣两个步骤
4️⃣、比如有个LHView,那么把这个事件交给这个view,再遍历LHView的子控件,直至没有更合适的为止
5️⃣、如果没有符合条件的子控件,那么就认为自己最适合处理这个事件
当一个试图接收到事件传递时,就会调用 - hitText方法,这个方法用于判断找出最合适的view,找到最合适的view就会调用响应的处理方法
代码如下
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//1、判断当前的view能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
//2、判断点在不在当前控件
NSInteger pointCount = self.subviews.count;
//3、从后往前遍历子控件
for (NSInteger i = pointCount - 1; i >= 0; i--) {
UIView * childView = self.subviews[i];
//把当前控件的坐标转换成子控件上的坐标
CGPoint childPoint = [self convertPoint:point toView:childView];
UIView * fitView = [childView hitTest:childPoint withEvent:event];
if (fitView) {
return fitView;
}
}
//循环结束,没有找到比自己更合适的view
return self;
}
总结:
事件的传递与响应(响应者链条)
1、当一个事件发生后,事件会从父控件传给子控件,也就是说有UIApplication -> UIWindow -> UIView -> initial view 的传递,寻找最合适的view的过程,当一个试图接收到事件之后就会调用 -(UIView *)hitTest:(CGpoint) point withEvent:(UIEvent *)event,经过5个步骤来寻找的过程。
2、事件的响应:首先看当前的initial view是否处理这个事件,如果不则会将事件传递给上级试图,如果上级试图仍然无法处理则会继续往上传递,一直传递到控制器 viewController。会判断试图控制器的根试图能否处理此事件,此时还是不能处理则交给UIWindow,如果还不能处理则交给最后的UIApplication,它如果还不能处理的话就丢弃
3、在事件的响应中,如果某个空间实现了touches....的方法,则这个事件将有此控件来接收,如果调用了【super touches。。。】;就会将事件顺着响应者链条网上往上传递,传递给上一个响应者,接着就会调用上一个响应者的touches方法。
====马上结束====
试问:能否做到一个事件多个对象处理
系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到这个目的
试问:事件的传递和响应的区别
事件的传递时从上到下,从父控件到子控件,事件的响应是从下往上,顺着响应者链条向上传递,子控件到父控件
====== 这回真的是马上结束 ======
-(UIResponder *)nextResponder;
它返回接收者下一个响应,没有就返回nil
结束
推荐阅读