iOS中的事件
在使用app过程中,会产生各种各样的事件
可以分为三大类
响应者对象
- iOS中只有继承了UIResponder才能接收并处理事件,称为响应者对象
- UIApplication、UIViewController、UIView都继承UIResponder。所以他们都是响应者对象,都能接收并处理事件
- UIResponder内部提供一些方法
触摸事件
注:如果处理UIView的触摸事件,必须要自定义UIView的子类,在子类中重写以下四个方法
//当手指开始触摸View时,系统会自动调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//当手指在view上移动时,系统自动调用
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//当手指在view上离开时,系统自动调用
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//结束触摸时,某个系统事件(例如电话呼入)会打断触摸过程,,系统自动调用
- (void)touchesCancelled:(NSSet<UITouch *> *)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的触摸事件处理
关于UITouch
- 当用户用一根手指触摸屏幕时,系统会创建一个与手指相关联的UITouche对象
一个手指对应一个UITouch对象(如果两个手指同时触摸一个view,那么view只会调用一次toucheBegan方法,touches包含两个UITouch对象)
UITouch的作用:
- 保存着和手指相关的信息,比如触摸的位置,时间,阶段
- 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保持该手指在触摸的位置
- 当手指离开屏幕时,系统会销毁相应的UITouch对象
注: iphone开发,要避免使用双击事件
UITouch的常用方法
返回值表示触摸在View上的位置
返回的位置是针对view的坐标(左上角为原点)
若传入参数的view是nil的话,返回的值表示在UIWindow的位置
- (CGPoint)locationInView:(nullable UIView *)view;
返回前一个触摸点的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;
关于实现UIView拖拽的代码
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//获取对象
UITouch *touch = [touches anyObject];
//当前手指的位置
CGPoint currentP = [touch locationInView:self];
//之前手指的位置
CGPoint previousP = [touch previousLocationInView:self];
//计算偏移量
CGFloat offsetX = currentP.x - previousP.x;
CGFloat offsetY = currentP.y - previousP.y;
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
关于UIEvent
- 每产生一个事件,就会产生一个UIEvent对象
- UIEvent:称为事件对象,记录产生的时刻和类型
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches,//触摸
UIEventTypeMotion,//加速器
UIEventTypeRemoteControl,//远程
UIEventTypePresses,3D touch
};
事件的产生和传递
事件的产生
- 发生触摸事件后,系统就会将该事件加入到一个由UIApplication管理的事件队列中
- UIApplication会从事件队列中找到最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
- 主窗口会在视图层次结构中找到一个最适合的视图来处理触摸事件,这也是整个事件处理过程的第一步
- 找到最适合的视图控件后,就会调用视图控件的touches方法来做具体的事件处理
UIApplicaton -> UIWindow -> UIViewControleler -> UIView -> subView
事件的传递
- 触摸事件的传递是从父控件传递到子控件
- 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
- UIView不能接收事件的三种情况
- 不接收用户交互
userInteractionEnabled = NO - 隐藏
hidden = YES - 透明
alpha = 0.0 ~0.01
注:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸时间的
- 不接收用户交互
找到最适合的View
- 当前View能否接收触摸事件
触摸点是否在当前View上
子视图数组从后往前(从视图的顶部往底部)遍历子控件,重复这俩个步骤
在查找最合适的view的两个最重要的方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
关于hitTest:withEvent:
当事件传递给一个控件,这个控件就会调用自己的hitTest:withEvent:方法,用于寻找并返回最合适的view, 它不管这个控件能不能处理事件也不管点是否在view上,事件都会先传给这view再调用这个view的hitTest方法。不管点击哪里,最合适的view都是hitTest返回的那个view。
注:
1、重写window的hitTest:withEvent:方法return nil,谁都不能处理事件,窗口也不能处理
2、控制器的view的hitTest:withEvent:方法,return nil或者window的hitTest:withEvent:方法,return self,只能由窗口处理事件。
3、调用当前hitTest:withEvent:方法的view不是合适的view,子控件也不是合适的view。如果同级的兄弟控件也没有合适的view,那么最合适的view就是父控件
pointInside:withEvent
判断点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。
最合适的view代码实现
UIApplication -> keyWindow ->view -> …
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha == 0) {
return nil;
}
//判断点在不在当前的控件
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i --) {
UIView *childView = self.subviews[i];
//把当前控件的坐标系改为控件上的坐标系
CGPoint childp =[self convertPoint:point toView:childView];
UIView *fifView = [childView hitTest:childp withEvent:event];
if (fifView) {
//寻找最适合的View
return fifView;
}
}
//当前的就是最适合的
return self;
}