iOS事件
概述
iOS中事件有触摸事件、加速计事件、远程控制事件,下面以触摸事件为例研究下iOS事件相关的内容
iOS事件分类
UIResponsder
iOS中继承了UIResponsder
的对象才能够响应事件,如UIViewController
、UIView
、UIApplication
。
UIResponsder能够响应事件的原因是:提供了几个方法来处理事件
//触摸事件
- (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;
备注:
- 当多个手指同时触摸屏幕时,只会调用一次
touchesBegan:withEvent:
方法,touches
中包含多个UITouch
对象,分别对应着触摸屏幕的手指 - 如果是
UIViewController
的事件,在UIViewController
中重写上面用到的方法即可;如果是UIView
的事件,需要在子类中重写上面用到的方法
实例:触摸屏幕,移动屏幕上的某个UIButton
在UIView
的子类中重写touchesMoved: withEvent:
方法
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];//当前点的位置
CGPoint previousPoint = [touch previousLocationInView:self];//上一个点的位置
CGFloat xPoint = point.x - previousPoint.x;
CGFloat yPoint = point.y - previousPoint.y;
NSLog(@"point:%@;previousPoint:%@;xPoint:%f;yPoint:%f",NSStringFromCGPoint(point), NSStringFromCGPoint(previousPoint), xPoint, yPoint);
CGPoint centerPoint = self.btn.center;
centerPoint = CGPointMake(centerPoint.x + xPoint, centerPoint.y + yPoint);
self.btn.center = centerPoint;
}
iOS 事件处理过程
iOS事件分为事件传递、事件响应两个过程。
事件的产生
发生触摸事件后,系统会将事件加入到由UIApplication管理的队列(先进先出FIFO),然后将队列最前面的事件分发下去:UIApplication->keyWindow->视图层次结构,找到合适的视图控件后,就会调用对应控件的事件方法(touchesBegan: withEvent:
等)来处理对应的事件。
事件的传递
触摸事件的传递是从父控件传递到子控件
- 寻找响应事件的控件步骤
- 首先判断keyWindow是否能响应事件
- 触摸点是否在自己身上
- 从后往前遍历子控件,重复前面的两个步骤(首先查找数组中最后一个元素)
- 如果没有符合条件的子控件,那就认为自己可以处理
- UIView不能响应事件的三种情况
- 不允许交互userInteractionEnabled = NO
- 隐藏的控件不能接收事件
- 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。
事件传递实例
注意:如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
查找符合条件的控件原理
主要是通过以下两个控件实现查找
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;//返回nil,表明当前view以及子view没有能够响应事件的;如果不为nil,则表明当前view中有能响应事件的
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;//返回YES,表明触摸点在当前view中
通过调用hitTest:withEvent:
可以判断是否有能够响应事件的控件,具体步骤为:
产生触摸事件->UIApplication事件队列->[UIWindow hitTest:withEvent:]->返回合适的view->[子控件 hitTest:withEvent:]->返回合适的view
不过子控件是否能够响应事件,父控件都会把事件传递给子控件,如果子控件不能响应事件,则父控件响应事件
实例:view中的button响应事件
参考iOS超出父视图后点击事件不响应,文中想要view中的button响应点击事件,因此重写了hitTest:withEvent:
方法
响应者链
在iOS程序中无论是最后面的UIWindow还是最前面的某个按钮,它们的摆放是有前后关系的,一个控件可以放到另一个控件上面或下面,那么用户点击某个控件时是触发上面的控件还是下面的控件呢,这种先后关系构成一个链条就叫“响应者链”。
事件响应过程
- 如果当前控件无法响应事件,就将事件按照视图层级结构(最上层是根视图)向上传递。如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
- 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
- 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
- 如果UIApplication也不能处理该事件或消息,则将其丢弃
总结
- 触摸屏幕产生触摸事件后,触摸事件会被添加到由UIApplication管理的事件队列中
- UIApplication会从事件队列中取出最前面的事件,把事件传递给keyWindow
- keyWindow会在视图层次结构中找到一个最合适的视图来处理触摸事件
- 最合适的view会调用自己的touches方法处理事件,如果不能处理事件,就将事件沿着响应者链向上抛